From e7fe9afe063ab8c6420825b082b8b9a39e4c6f46 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 7 Aug 2020 14:28:13 -0400 Subject: [PATCH 001/517] Convert subscription constructors to use named arguments. --- src/AbstractSubscription.js | 22 +- src/CombinedSubscription.js | 52 +- src/HistoricalSubscription.js | 26 +- src/RealTimeSubscription.js | 25 +- src/StreamrClient.js | 48 +- src/Subscription.js | 11 +- test/integration/StreamrClient.test.js | 224 ++++---- test/unit/CombinedSubscription.test.js | 13 +- test/unit/HistoricalSubscription.test.js | 539 ++++++++++++++------ test/unit/KeyExchangeUtil.test.js | 2 +- test/unit/KeyHistoryStorageUtil.test.js | 2 +- test/unit/LatestKeyStorageUtil.test.js | 2 +- test/unit/MessageCreationUtil.test.js | 10 +- test/unit/RealTimeSubscription.test.js | 405 +++++++++++---- test/unit/StreamrClient.test.js | 124 ++--- test/unit/SubscribedStreamPartition.test.js | 7 +- 16 files changed, 1037 insertions(+), 475 deletions(-) diff --git a/src/AbstractSubscription.js b/src/AbstractSubscription.js index 8f49b4b29..cd6870157 100644 --- a/src/AbstractSubscription.js +++ b/src/AbstractSubscription.js @@ -13,8 +13,26 @@ function decryptErrorToDisplay(error) { } export default class AbstractSubscription extends Subscription { - constructor(streamId, streamPartition, callback, groupKeys, propagationTimeout, resendTimeout, orderMessages = true, onUnableToDecrypt, debug) { - super(streamId, streamPartition, callback, groupKeys, propagationTimeout, resendTimeout, debug) + constructor({ + streamId, + streamPartition, + callback, + groupKeys, + onUnableToDecrypt, + propagationTimeout, + resendTimeout, + orderMessages = true, + debug, + }) { + super({ + streamId, + streamPartition, + callback, + groupKeys, + propagationTimeout, + resendTimeout, + debug, + }) this.callback = callback this.pendingResendRequestIds = {} this._lastMessageHandlerPromise = {} diff --git a/src/CombinedSubscription.js b/src/CombinedSubscription.js index ad143b6d6..f02fe45d1 100644 --- a/src/CombinedSubscription.js +++ b/src/CombinedSubscription.js @@ -4,12 +4,41 @@ import Subscription from './Subscription' import AbstractSubscription from './AbstractSubscription' export default class CombinedSubscription extends Subscription { - constructor(streamId, streamPartition, callback, options, groupKeys, propagationTimeout, resendTimeout, orderMessages = true, - onUnableToDecrypt = AbstractSubscription.defaultUnableToDecrypt, debug) { - super(streamId, streamPartition, callback, groupKeys, propagationTimeout, resendTimeout) + constructor({ + streamId, + streamPartition, + callback, + options, + propagationTimeout, + groupKeys, + onUnableToDecrypt, + resendTimeout, + orderMessages = true, + debug, + }) { + super({ + streamId, + streamPartition, + callback, + groupKeys, + onUnableToDecrypt, + propagationTimeout, + resendTimeout, + debug, + }) - this.sub = new HistoricalSubscription(streamId, streamPartition, callback, options, - groupKeys, this.propagationTimeout, this.resendTimeout, orderMessages, onUnableToDecrypt, debug) + this.sub = new HistoricalSubscription({ + streamId, + streamPartition, + callback, + options, + groupKeys, + onUnableToDecrypt, + propagationTimeout: this.propagationTimeout, + resendTimeout: this.resendTimeout, + orderMessages, + debug: this.debug, + }) this.realTimeMsgsQueue = [] this.sub.on('message received', (msg) => { if (msg) { @@ -18,8 +47,17 @@ export default class CombinedSubscription extends Subscription { }) this.sub.on('initial_resend_done', async () => { this._unbindListeners(this.sub) - const realTime = new RealTimeSubscription(streamId, streamPartition, callback, - groupKeys, this.propagationTimeout, this.resendTimeout, orderMessages, onUnableToDecrypt, debug) + const realTime = new RealTimeSubscription({ + streamId, + streamPartition, + callback, + groupKeys, + onUnableToDecrypt, + propagationTimeout: this.propagationTimeout, + resendTimeout: this.resendTimeout, + orderMessages, + debug: this.debug, + }) this._bindListeners(realTime) if (this.sub.orderingUtil) { realTime.orderingUtil.orderedChains = this.sub.orderingUtil.orderedChains diff --git a/src/HistoricalSubscription.js b/src/HistoricalSubscription.js index a319db3c6..542585d93 100644 --- a/src/HistoricalSubscription.js +++ b/src/HistoricalSubscription.js @@ -6,9 +6,29 @@ import DecryptionKeySequence from './DecryptionKeySequence' const { StreamMessage } = MessageLayer export default class HistoricalSubscription extends AbstractSubscription { - constructor(streamId, streamPartition, callback, options, groupKeys, propagationTimeout, resendTimeout, orderMessages = true, - onUnableToDecrypt = AbstractSubscription.defaultUnableToDecrypt) { - super(streamId, streamPartition, callback, groupKeys, propagationTimeout, resendTimeout, orderMessages, onUnableToDecrypt) + constructor({ + streamId, + streamPartition, + callback, + groupKeys, + onUnableToDecrypt = AbstractSubscription.defaultUnableToDecrypt, + options, + propagationTimeout, + resendTimeout, + orderMessages = true, + debug + }) { + super({ + streamId, + streamPartition, + callback, + groupKeys, + onUnableToDecrypt, + propagationTimeout, + resendTimeout, + orderMessages, + debug, + }) this.resendOptions = options if (!this.resendOptions || (!this.resendOptions.from && !this.resendOptions.last)) { throw new Error('Resend options (either "from", "from" and "to", or "last") must be defined in a historical subscription.') diff --git a/src/RealTimeSubscription.js b/src/RealTimeSubscription.js index e460943a9..384970f55 100644 --- a/src/RealTimeSubscription.js +++ b/src/RealTimeSubscription.js @@ -7,9 +7,28 @@ import EncryptionUtil from './EncryptionUtil' import UnableToDecryptError from './errors/UnableToDecryptError' export default class RealTimeSubscription extends AbstractSubscription { - constructor(streamId, streamPartition, callback, groupKeys, propagationTimeout, resendTimeout, orderMessages = true, - onUnableToDecrypt = AbstractSubscription.defaultUnableToDecrypt, debug) { - super(streamId, streamPartition, callback, groupKeys, propagationTimeout, resendTimeout, orderMessages, onUnableToDecrypt) + constructor({ + streamId, + streamPartition, + callback, + groupKeys, + onUnableToDecrypt = AbstractSubscription.defaultUnableToDecrypt, + propagationTimeout, + resendTimeout, + orderMessages = true, + debug, + }) { + super({ + streamId, + streamPartition, + callback, + groupKeys, + onUnableToDecrypt, + propagationTimeout, + resendTimeout, + orderMessages, + debug, + }) const id = uniqueId('Subscription') if (debug) { diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 710a8a68c..36d03b71e 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -477,10 +477,18 @@ export default class StreamrClient extends EventEmitter { } await this.ensureConnected() - - const sub = new HistoricalSubscription(options.stream, options.partition || 0, callback, options.resend, - this.options.subscriberGroupKeys[options.stream], this.options.gapFillTimeout, this.options.retryResendAfter, - this.options.orderMessages, options.onUnableToDecrypt, this.debug) + const sub = new HistoricalSubscription({ + streamId: options.stream, + streamPartition: options.partition || 0, + callback, + options: options.resend, + groupKeys: this.options.subscriberGroupKeys[options.stream], + onUnableToDecrypt: options.onUnableToDecrypt, + propagationTimeout: this.options.gapFillTimeout, + resendTimeout: this.options.retryResendAfter, + orderMessages: this.orderMessages, + debug: this.debug, + }) // TODO remove _addSubscription after uncoupling Subscription and Resend sub.setState(Subscription.State.subscribed) @@ -547,15 +555,31 @@ export default class StreamrClient extends EventEmitter { // Create the Subscription object and bind handlers let sub if (options.resend) { - sub = new CombinedSubscription( - options.stream, options.partition || 0, callback, options.resend, - groupKeys, this.options.gapFillTimeout, this.options.retryResendAfter, - this.options.orderMessages, options.onUnableToDecrypt, this.debug, - ) + sub = new CombinedSubscription({ + streamId: options.stream, + streamPartition: options.partition || 0, + callback, + options: options.resend, + groupKeys, + onUnableToDecrypt: options.onUnableToDecrypt, + propagationTimeout: this.options.gapFillTimeout, + resendTimeout: this.options.retryResendAfter, + orderMessages: this.options.orderMessages, + debug: this.debug, + }) } else { - sub = new RealTimeSubscription(options.stream, options.partition || 0, callback, - groupKeys, this.options.gapFillTimeout, this.options.retryResendAfter, - this.options.orderMessages, options.onUnableToDecrypt, this.debug) + sub = new RealTimeSubscription({ + streamId: options.stream, + streamPartition: options.partition || 0, + callback, + options: options.resend, + groupKeys, + onUnableToDecrypt: options.onUnableToDecrypt, + propagationTimeout: this.options.gapFillTimeout, + resendTimeout: this.options.retryResendAfter, + orderMessages: this.options.orderMessages, + debug: this.debug, + }) } sub.on('gap', (from, to, publisherId, msgChainId) => { if (!sub.resending) { diff --git a/src/Subscription.js b/src/Subscription.js index bb959f735..17375a861 100644 --- a/src/Subscription.js +++ b/src/Subscription.js @@ -9,8 +9,15 @@ const DEFAULT_RESEND_TIMEOUT = 5000 'interface' containing the default parameters and functionalities common to every subscription (Combined, RealTime and Historical) */ export default class Subscription extends EventEmitter { - constructor(streamId, streamPartition, callback, groupKeys, - propagationTimeout = DEFAULT_PROPAGATION_TIMEOUT, resendTimeout = DEFAULT_RESEND_TIMEOUT, debug) { + constructor({ + streamId, + streamPartition, + callback, + groupKeys, + propagationTimeout = DEFAULT_PROPAGATION_TIMEOUT, + resendTimeout = DEFAULT_RESEND_TIMEOUT, + debug + }) { super() if (!callback) { diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index 06babb0cf..d226540c2 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -947,117 +947,6 @@ describe('StreamrClient', () => { }) }) }, 30000) - - describe.skip('decryption', () => { - it('client.subscribe can decrypt encrypted messages if it knows the group key', async (done) => { - client.once('error', done) - const id = Date.now() - const publisherId = await client.getPublisherId() - const groupKey = crypto.randomBytes(32) - const keys = {} - keys[publisherId] = groupKey - const sub = client.subscribe({ - stream: stream.id, - groupKeys: keys, - }, (parsedContent, streamMessage) => { - assert.equal(parsedContent.id, id) - - // Check signature stuff - assert.strictEqual(streamMessage.signatureType, StreamMessage.SIGNATURE_TYPES.ETH) - assert(streamMessage.getPublisherId()) - assert(streamMessage.signature) - - // All good, unsubscribe - client.unsubscribe(sub) - sub.once('unsubscribed', () => { - done() - }) - }) - - // Publish after subscribed - sub.once('subscribed', () => { - client.publish(stream.id, { - id, - }, Date.now(), null, groupKey) - }) - }) - - it('client.subscribe can get the group key and decrypt encrypted messages using an RSA key pair', async (done) => { - client.once('error', done) - const id = Date.now() - const groupKey = crypto.randomBytes(32) - // subscribe without knowing the group key to decrypt stream messages - const sub = client.subscribe({ - stream: stream.id, - }, (parsedContent, streamMessage) => { - assert.equal(parsedContent.id, id) - - // Check signature stuff - assert.strictEqual(streamMessage.signatureType, StreamMessage.SIGNATURE_TYPES.ETH) - assert(streamMessage.getPublisherId()) - assert(streamMessage.signature) - - // Now the subscriber knows the group key - assert.deepStrictEqual(sub.groupKeys[streamMessage.getPublisherId().toLowerCase()], groupKey) - - sub.once('unsubscribed', () => { - done() - }) - - // All good, unsubscribe - client.unsubscribe(sub) - }) - - // Publish after subscribed - sub.once('subscribed', () => { - client.publish(stream.id, { - id, - }, Date.now(), null, groupKey) - }) - }, 2 * TIMEOUT) - - it('client.subscribe with resend last can get the historical keys for previous encrypted messages', (done) => { - client.once('error', done) - // Publish encrypted messages with different keys - const groupKey1 = crypto.randomBytes(32) - const groupKey2 = crypto.randomBytes(32) - client.publish(stream.id, { - test: 'resent msg 1', - }, Date.now(), null, groupKey1) - client.publish(stream.id, { - test: 'resent msg 2', - }, Date.now(), null, groupKey2) - - // Add delay: this test needs some time to allow the message to be written to Cassandra - let receivedFirst = false - setTimeout(() => { - // subscribe with resend without knowing the historical keys - const sub = client.subscribe({ - stream: stream.id, - resend: { - last: 2, - }, - }, async (parsedContent) => { - // Check message content - if (!receivedFirst) { - assert.strictEqual(parsedContent.test, 'resent msg 1') - receivedFirst = true - } else { - assert.strictEqual(parsedContent.test, 'resent msg 2') - } - - sub.once('unsubscribed', () => { - // TODO: fix this hack in other PR - assert.strictEqual(client.subscribedStreamPartitions[stream.id + '0'], undefined) - done() - }) - - // All good, unsubscribe - client.unsubscribe(sub) - }) - }, TIMEOUT * 0.8) - }, 2 * TIMEOUT) - }) }) describe('utf-8 encoding', () => { @@ -1090,4 +979,117 @@ describe('StreamrClient', () => { }) }, 10000) }) + + describe('decryption', () => { + it('client.subscribe can decrypt encrypted messages if it knows the group key', async (done) => { + client.once('error', done) + const id = Date.now() + const publisherId = await client.getPublisherId() + const groupKey = crypto.randomBytes(32) + const keys = { + [publisherId]: groupKey, + } + + const sub = client.subscribe({ + stream: stream.id, + groupKeys: keys, + }, (parsedContent, streamMessage) => { + assert.equal(parsedContent.id, id) + + // Check signature stuff + assert.strictEqual(streamMessage.signatureType, StreamMessage.SIGNATURE_TYPES.ETH) + assert(streamMessage.getPublisherId()) + assert(streamMessage.signature) + + // All good, unsubscribe + client.unsubscribe(sub) + sub.once('unsubscribed', () => { + done() + }) + }) + + // Publish after subscribed + sub.once('subscribed', () => { + client.publish(stream.id, { + id, + }, Date.now(), null, groupKey) + }) + }) + + it('client.subscribe can get the group key and decrypt encrypted messages using an RSA key pair', async (done) => { + client.once('error', done) + const id = Date.now() + const groupKey = crypto.randomBytes(32) + // subscribe without knowing the group key to decrypt stream messages + const sub = client.subscribe({ + stream: stream.id, + }, (parsedContent, streamMessage) => { + assert.equal(parsedContent.id, id) + + // Check signature stuff + assert.strictEqual(streamMessage.signatureType, StreamMessage.SIGNATURE_TYPES.ETH) + assert(streamMessage.getPublisherId()) + assert(streamMessage.signature) + + // Now the subscriber knows the group key + assert.deepStrictEqual(sub.groupKeys[streamMessage.getPublisherId().toLowerCase()], groupKey) + + sub.once('unsubscribed', () => { + done() + }) + + // All good, unsubscribe + client.unsubscribe(sub) + }) + + // Publish after subscribed + sub.once('subscribed', () => { + client.publish(stream.id, { + id, + }, Date.now(), null, groupKey) + }) + }, 2 * TIMEOUT) + + it('client.subscribe with resend last can get the historical keys for previous encrypted messages', (done) => { + client.once('error', done) + // Publish encrypted messages with different keys + const groupKey1 = crypto.randomBytes(32) + const groupKey2 = crypto.randomBytes(32) + client.publish(stream.id, { + test: 'resent msg 1', + }, Date.now(), null, groupKey1) + client.publish(stream.id, { + test: 'resent msg 2', + }, Date.now(), null, groupKey2) + + // Add delay: this test needs some time to allow the message to be written to Cassandra + let receivedFirst = false + setTimeout(() => { + // subscribe with resend without knowing the historical keys + const sub = client.subscribe({ + stream: stream.id, + resend: { + last: 2, + }, + }, async (parsedContent) => { + // Check message content + if (!receivedFirst) { + assert.strictEqual(parsedContent.test, 'resent msg 1') + receivedFirst = true + } else { + assert.strictEqual(parsedContent.test, 'resent msg 2') + } + + sub.once('unsubscribed', () => { + // TODO: fix this hack in other PR + assert.strictEqual(client.subscribedStreamPartitions[stream.id + '0'], undefined) + done() + }) + + // All good, unsubscribe + client.unsubscribe(sub) + }) + }, TIMEOUT * 0.8) + }, 2 * TIMEOUT) + }) }) diff --git a/test/unit/CombinedSubscription.test.js b/test/unit/CombinedSubscription.test.js index 2d729a5a3..c53aa374f 100644 --- a/test/unit/CombinedSubscription.test.js +++ b/test/unit/CombinedSubscription.test.js @@ -27,9 +27,16 @@ const msg1 = createMsg() describe('CombinedSubscription', () => { it('handles real time gap that occurred during initial resend', (done) => { const msg4 = createMsg(4, undefined, 3) - const sub = new CombinedSubscription(msg1.getStreamId(), msg1.getStreamPartition(), sinon.stub(), { - last: 1 - }, {}, 100, 100) + const sub = new CombinedSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: sinon.stub(), + options: { + last: 1 + }, + propagationTimeout: 100, + resendTimeout: 100, + }) sub.on('error', done) sub.addPendingResendRequestId('requestId') sub.on('gap', (from, to, publisherId) => { diff --git a/test/unit/HistoricalSubscription.test.js b/test/unit/HistoricalSubscription.test.js index 883ebec7f..162c72634 100644 --- a/test/unit/HistoricalSubscription.test.js +++ b/test/unit/HistoricalSubscription.test.js @@ -36,11 +36,16 @@ describe('HistoricalSubscription', () => { describe('message handling', () => { describe('handleBroadcastMessage()', () => { it('calls the message handler', () => { - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), (content, receivedMsg) => { - expect(content).toEqual(msg.getParsedContent()) - expect(msg).toEqual(receivedMsg) - }, { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: (content, receivedMsg) => { + expect(content).toEqual(msg.getParsedContent()) + expect(msg).toEqual(receivedMsg) + }, + options: { + last: 1, + }, }) return sub.handleResentMessage(msg, 'requestId', sinon.stub().resolves(true)) }) @@ -51,8 +56,14 @@ describe('HistoricalSubscription', () => { beforeEach(() => { const msgHandler = () => { throw new Error('should not be called!') } - sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), msgHandler, { - last: 1, + + sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: msgHandler, + options: { + last: 1, + }, }) stdError = console.error console.error = sinon.stub() @@ -101,14 +112,19 @@ describe('HistoricalSubscription', () => { const received = [] - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), (content, receivedMsg) => { - received.push(receivedMsg) - if (received.length === 5) { - expect(msgs).toEqual(received) - done() - } - }, { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: (content, receivedMsg) => { + received.push(receivedMsg) + if (received.length === 5) { + expect(msgs).toEqual(received) + done() + } + }, + options: { + last: 1, + }, }) return Promise.all(msgs.map((m) => sub.handleResentMessage(m, 'requestId', sinon.stub().resolves(true)))) @@ -122,14 +138,19 @@ describe('HistoricalSubscription', () => { const received = [] - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), (content, receivedMsg) => { - received.push(receivedMsg) - if (received.length === 5) { - expect(received).toEqual(msgs) - done() - } - }, { - last: 5, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: (content, receivedMsg) => { + received.push(receivedMsg) + if (received.length === 5) { + expect(received).toEqual(msgs) + done() + } + }, + options: { + last: 5, + }, }) return Promise.all(msgs.map((m, index, arr) => sub.handleResentMessage(m, 'requestId', async () => { @@ -152,19 +173,24 @@ describe('HistoricalSubscription', () => { resolveLastMessageValidation = resolve }) - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), (content, receivedMsg) => { - received.push(receivedMsg) - if (received.length === 4) { - // only resolve last message when 4th message received - resolveLastMessageValidation() - } + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: (content, receivedMsg) => { + received.push(receivedMsg) + if (received.length === 4) { + // only resolve last message when 4th message received + resolveLastMessageValidation() + } - if (received.length === 5) { - expect(received).toEqual(msgs) - done() + if (received.length === 5) { + expect(received).toEqual(msgs) + done() + } + }, + options: { + last: 5, } - }, { - last: 5, }) return Promise.all(msgs.map((m, index, arr) => sub.handleResentMessage(m, 'requestId', async () => { @@ -181,8 +207,13 @@ describe('HistoricalSubscription', () => { describe('duplicate handling', () => { it('ignores re-received messages', async () => { const handler = sinon.stub() - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), handler, { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: handler, + options: { + last: 1, + } }) await sub.handleResentMessage(msg, 'requestId', sinon.stub().resolves(true)) @@ -197,9 +228,17 @@ describe('HistoricalSubscription', () => { const msg1 = msg const msg4 = createMsg(4, undefined, 3) - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), sinon.stub(), { - last: 1, - }, {}, 100, 100) + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub(), + options: { + last: 1, + }, + propagationTimeout: 100, + resendTimeout: 100, + }) + sub.on('gap', (from, to, publisherId) => { expect(from.timestamp).toEqual(1) expect(from.sequenceNumber).toEqual(1) @@ -216,29 +255,33 @@ describe('HistoricalSubscription', () => { sub.handleResentMessage(msg4, 'requestId', sinon.stub().resolves(true)) }) - it( - 'emits second "gap" after the first one if no missing message is received in between', - (done) => { - const msg1 = msg - const msg4 = createMsg(4, undefined, 3) + it('emits second "gap" after the first one if no missing message is received in between', (done) => { + const msg1 = msg + const msg4 = createMsg(4, undefined, 3) - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), sinon.stub(), { + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub(), + options: { last: 1, - }, {}, 100, 100) - sub.on('gap', (from, to, publisherId) => { - sub.on('gap', (from2, to2, publisherId2) => { - expect(from).toStrictEqual(from2) - expect(to).toStrictEqual(to2) - expect(publisherId).toStrictEqual(publisherId2) - sub.stop() - done() - }) + }, + propagationTimeout: 100, + resendTimeout: 100, + }) + sub.once('gap', (from, to, publisherId) => { + sub.once('gap', (from2, to2, publisherId2) => { + expect(from).toStrictEqual(from2) + expect(to).toStrictEqual(to2) + expect(publisherId).toStrictEqual(publisherId2) + sub.stop() + done() }) + }) - sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) - sub.handleResentMessage(msg4, 'requestId', sinon.stub().resolves(true)) - } - ) + sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) + sub.handleResentMessage(msg4, 'requestId', sinon.stub().resolves(true)) + }) it('does not emit second "gap" after the first one if the missing messages are received in between', (done) => { const msg1 = msg @@ -246,9 +289,17 @@ describe('HistoricalSubscription', () => { const msg3 = createMsg(3, undefined, 2) const msg4 = createMsg(4, undefined, 3) - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), sinon.stub(), { - last: 1, - }, {}, 100, 100) + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub(), + options: { + last: 1, + }, + propagationTimeout: 100, + resendTimeout: 100, + }) + sub.once('gap', () => { sub.handleResentMessage(msg2, 'requestId', sinon.stub().resolves(true)) sub.handleResentMessage(msg3, 'requestId', sinon.stub().resolves(true)).then(() => {}) @@ -267,9 +318,17 @@ describe('HistoricalSubscription', () => { const msg1 = msg const msg4 = createMsg(4, undefined, 3) - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), sinon.stub(), { - last: 1, - }, {}, 100, 100) + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub(), + options: { + last: 1, + }, + propagationTimeout: 100, + resendTimeout: 100, + }) + sub.once('gap', () => { sub.emit('unsubscribed') sub.once('gap', () => { throw new Error('should not emit second gap') }) @@ -286,10 +345,16 @@ describe('HistoricalSubscription', () => { it('does not emit second "gap" if gets disconnected', async (done) => { const msg1 = msg const msg4 = createMsg(4, undefined, 3) - - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), sinon.stub(), { - last: 1, - }, {}, 100, 100) + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub(), + options: { + last: 1, + }, + propagationTimeout: 100, + resendTimeout: 100, + }) sub.once('gap', () => { sub.emit('disconnected') sub.once('gap', () => { throw new Error('should not emit second gap') }) @@ -307,9 +372,17 @@ describe('HistoricalSubscription', () => { const msg1 = msg const msg1b = createMsg(1, 0, undefined, 0, {}, 'anotherPublisherId') - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), sinon.stub(), { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub(), + options: { + last: 1, + }, + propagationTimeout: 100, + resendTimeout: 100, }) + sub.on('gap', () => { throw new Error('unexpected gap') }) @@ -322,9 +395,17 @@ describe('HistoricalSubscription', () => { const msg1 = msg const msg4 = createMsg(1, 4, 1, 3) - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), sinon.stub(), { - last: 1, - }, {}, 100, 100) + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub(), + options: { + last: 1, + }, + propagationTimeout: 100, + resendTimeout: 100, + }) + sub.once('gap', (from, to, publisherId) => { expect(from.timestamp).toEqual(1) // cannot know the first missing message so there will be a duplicate received expect(from.sequenceNumber).toEqual(1) @@ -345,8 +426,15 @@ describe('HistoricalSubscription', () => { const msg1 = msg const msg2 = createMsg(2, undefined, 1) - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), sinon.stub(), { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub(), + options: { + last: 1, + }, + propagationTimeout: 100, + resendTimeout: 100, }) sub.on('gap', sinon.stub().throws()) @@ -358,9 +446,17 @@ describe('HistoricalSubscription', () => { const msg1 = msg const msg2 = createMsg(1, 1, 1, 0) - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), sinon.stub(), { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub(), + options: { + last: 1, + }, + propagationTimeout: 100, + resendTimeout: 100, }) + sub.once('gap', sinon.stub().throws()) sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) @@ -375,11 +471,20 @@ describe('HistoricalSubscription', () => { const msg4 = createMsg(4, 0, 3, 0) const received = [] - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), (content, receivedMsg) => { - received.push(receivedMsg) - }, { - last: 1, - }, {}, 100, 100, false) + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: (content, receivedMsg) => { + received.push(receivedMsg) + }, + options: { + last: 1, + }, + propagationTimeout: 100, + resendTimeout: 100, + orderMessages: false, + }) + sub.on('gap', sinon.stub().throws()) await sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) @@ -399,10 +504,18 @@ describe('HistoricalSubscription', () => { const msg4 = createMsg(4, 0, 3, 0) const received = [] - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), (content, receivedMsg) => { - received.push(receivedMsg) - }, { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: (content, receivedMsg) => { + received.push(receivedMsg) + }, + options: { + last: 1, + }, + propagationTimeout: 100, + resendTimeout: 100, + orderMessages: true, }) await sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) @@ -421,11 +534,19 @@ describe('HistoricalSubscription', () => { const byeMsg = createMsg(1, undefined, null, null, { _bye: true, }) + const handler = sinon.stub() - const sub = new HistoricalSubscription(byeMsg.getStreamId(), byeMsg.getStreamPartition(), handler, { - last: 1, + const sub = new HistoricalSubscription({ + streamId: byeMsg.getStreamId(), + streamPartition: byeMsg.getStreamPartition(), + callback: handler, + options: { + last: 1, + }, + propagationTimeout: 100, + resendTimeout: 100, }) - sub.on('done', () => { + sub.once('done', () => { expect(handler.calledOnce).toBeTruthy() done() }) @@ -444,11 +565,16 @@ describe('HistoricalSubscription', () => { const msg1 = createMsg(1, 0, null, 0, { foo: 'bar', }) - sub = new HistoricalSubscription(msg1.getStreamId(), msg1.getStreamPartition(), (content) => { - expect(content).toStrictEqual(msg1.getParsedContent()) - done() - }, { - last: 1, + sub = new HistoricalSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: (content) => { + expect(content).toStrictEqual(msg1.getParsedContent()) + done() + }, + options: { + last: 1, + }, }) return sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) }) @@ -460,13 +586,19 @@ describe('HistoricalSubscription', () => { } const msg1 = createMsg(1, 0, null, 0, data) EncryptionUtil.encryptStreamMessage(msg1, groupKey) - sub = new HistoricalSubscription(msg1.getStreamId(), msg1.getStreamPartition(), (content) => { - expect(content).toStrictEqual(data) - done() - }, { - last: 1, - }, { - publisherId: groupKey, + sub = new HistoricalSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: (content) => { + expect(content).toStrictEqual(data) + done() + }, + options: { + last: 1, + }, + groupKeys: { + publisherId: groupKey, + } }) return sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) }) @@ -477,8 +609,13 @@ describe('HistoricalSubscription', () => { foo: 'bar', }) EncryptionUtil.encryptStreamMessage(msg1, correctGroupKey) - sub = new HistoricalSubscription(msg1.getStreamId(), msg1.getStreamPartition(), sinon.stub(), { - last: 1, + sub = new HistoricalSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: () => {}, + options: { + last: 1, + }, }) sub.on('groupKeyMissing', (publisherId, start, end) => { expect(publisherId).toBe(msg1.getPublisherId()) @@ -504,14 +641,19 @@ describe('HistoricalSubscription', () => { EncryptionUtil.encryptStreamMessage(msg2, groupKey2) let received1 = null let received2 = null - sub = new HistoricalSubscription(msg1.getStreamId(), msg1.getStreamPartition(), (content) => { - if (!received1) { - received1 = content - } else { - received2 = content - } - }, { - last: 1, + sub = new HistoricalSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: (content) => { + if (!received1) { + received1 = content + } else { + received2 = content + } + }, + options: { + last: 1, + }, }) // cannot decrypt msg1, queues it and emits "groupKeyMissing" (should send group key request). await sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) @@ -538,12 +680,18 @@ describe('HistoricalSubscription', () => { EncryptionUtil.encryptStreamMessage(msg1, correctGroupKey) EncryptionUtil.encryptStreamMessage(msg2, correctGroupKey) let undecryptableMsg = null - sub = new HistoricalSubscription(msg1.getStreamId(), msg1.getStreamPartition(), () => { - throw new Error('should not call the handler') - }, { - last: 1, - }, {}, 5000, 5000, true, (error) => { - undecryptableMsg = error.streamMessage + sub = new HistoricalSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: () => { + throw new Error('should not call the handler') + }, + options: { + last: 1, + }, + onUnableToDecrypt: (error) => { + undecryptableMsg = error.streamMessage + } }) // cannot decrypt msg1, emits "groupKeyMissing" (should send group key request). await sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) @@ -580,10 +728,15 @@ describe('HistoricalSubscription', () => { EncryptionUtil.encryptStreamMessage(msg3, groupKey3) EncryptionUtil.encryptStreamMessage(msg4, groupKey3) const received = [] - sub = new HistoricalSubscription(msg1.getStreamId(), msg1.getStreamPartition(), (content) => { - received.push(content) - }, { - last: 1, + sub = new HistoricalSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: (content) => { + received.push(content) + }, + options: { + last: 1, + }, }) // cannot decrypt msg1, queues it and emits "groupKeyMissing" (should send group key request). await sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) @@ -608,13 +761,14 @@ describe('HistoricalSubscription', () => { describe('handleError()', () => { it('emits an error event', (done) => { const err = new Error('Test error') - const sub = new HistoricalSubscription( - msg.getStreamId(), - msg.getStreamPartition(), - sinon.stub().throws('Msg handler should not be called!'), { + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub().throws('Msg handler should not be called!'), + options: { last: 1, - } - ) + }, + }) sub.onError = jest.fn() sub.once('error', (thrown) => { expect(thrown).toBe(err) @@ -625,13 +779,18 @@ describe('HistoricalSubscription', () => { }) it('marks the message as received if an InvalidJsonError occurs, and continue normally on next message', async (done) => { - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), (content, receivedMsg) => { - if (receivedMsg.getTimestamp() === 3) { - sub.stop() - done() - } - }, { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: (content, receivedMsg) => { + if (receivedMsg.getTimestamp() === 3) { + sub.stop() + done() + } + }, + options: { + last: 1, + }, }) sub.onError = jest.fn() @@ -654,9 +813,16 @@ describe('HistoricalSubscription', () => { }) it('if an InvalidJsonError AND a gap occur, does not mark it as received and emits gap at the next message', async (done) => { - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), sinon.stub(), { - last: 1, - }, {}, 100, 100) + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub(), + options: { + last: 1, + }, + propagationTimeout: 100, + resendTimeout: 100, + }) sub.onError = jest.fn() @@ -691,16 +857,26 @@ describe('HistoricalSubscription', () => { describe('setState()', () => { it('updates the state', () => { - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), sinon.stub(), { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub(), + options: { + last: 1, + }, }) sub.setState(Subscription.State.subscribed) expect(sub.getState()).toEqual(Subscription.State.subscribed) }) it('fires an event', (done) => { - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), sinon.stub(), { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub(), + options: { + last: 1, + }, }) sub.once(Subscription.State.subscribed, done) sub.setState(Subscription.State.subscribed) @@ -709,8 +885,13 @@ describe('HistoricalSubscription', () => { describe('handleResending()', () => { it('emits the resending event', async () => { - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), sinon.stub(), { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub(), + options: { + last: 1, + }, }) sub.addPendingResendRequestId('requestId') const onResending = new Promise((resolve) => sub.once('resending', resolve)) @@ -726,8 +907,13 @@ describe('HistoricalSubscription', () => { describe('handleResent()', () => { it('emits the "resent" + "initial_resend_done" events on last message (message handler completes BEFORE resent)', async (done) => { const handler = sinon.stub() - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), handler, { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: handler, + options: { + last: 1, + }, }) sub.addPendingResendRequestId('requestId') @@ -742,8 +928,13 @@ describe('HistoricalSubscription', () => { it('arms the Subscription to emit the resent event on last message (message handler completes AFTER resent)', async () => { const handler = sinon.stub() - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), handler, { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: handler, + options: { + last: 1, + }, }) sub.addPendingResendRequestId('requestId') const onResent = new Promise((resolve) => sub.once('resent', resolve)) @@ -758,8 +949,13 @@ describe('HistoricalSubscription', () => { it('should not emit "initial_resend_done" after receiving "resent" if there are still pending resend requests', async () => { const handler = sinon.stub() - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), handler, { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: handler, + options: { + last: 1, + }, }) sub.addPendingResendRequestId('requestId1') sub.addPendingResendRequestId('requestId2') @@ -778,8 +974,13 @@ describe('HistoricalSubscription', () => { it('emits 2 "resent" and 1 "initial_resend_done" after receiving 2 pending resend response', async (done) => { const handler = sinon.stub() - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), handler, { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: handler, + options: { + last: 1, + }, }) sub.addPendingResendRequestId('requestId1') sub.addPendingResendRequestId('requestId2') @@ -807,8 +1008,13 @@ describe('HistoricalSubscription', () => { it('can handle a second resend while in the middle of resending', async (done) => { const handler = sinon.stub() - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), handler, { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: handler, + options: { + last: 1, + }, }) sub.addPendingResendRequestId('requestId1') sub.addPendingResendRequestId('requestId2') @@ -829,8 +1035,13 @@ describe('HistoricalSubscription', () => { describe('handleNoResend()', () => { it('emits the no_resend event and then the initial_resend_done event', (done) => { - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), sinon.stub(), { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: sinon.stub(), + options: { + last: 1, + }, }) sub.addPendingResendRequestId('requestId') sub.once('no_resend', () => sub.once('initial_resend_done', () => done())) @@ -843,8 +1054,13 @@ describe('HistoricalSubscription', () => { it('should not emit "initial_resend_done" after receiving "no resend" if there are still pending resend requests', async (done) => { const handler = sinon.stub() - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), handler, { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: handler, + options: { + last: 1, + }, }) sub.addPendingResendRequestId('requestId1') sub.addPendingResendRequestId('requestId2') @@ -862,8 +1078,13 @@ describe('HistoricalSubscription', () => { it('emits 2 "resent" and 1 "initial_resend_done" after receiving 2 pending resend response', async (done) => { const handler = sinon.stub() - const sub = new HistoricalSubscription(msg.getStreamId(), msg.getStreamPartition(), handler, { - last: 1, + const sub = new HistoricalSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: handler, + options: { + last: 1, + }, }) sub.addPendingResendRequestId('requestId1') sub.addPendingResendRequestId('requestId2') diff --git a/test/unit/KeyExchangeUtil.test.js b/test/unit/KeyExchangeUtil.test.js index b9eca220c..d61556660 100644 --- a/test/unit/KeyExchangeUtil.test.js +++ b/test/unit/KeyExchangeUtil.test.js @@ -42,7 +42,7 @@ async function setupClient() { return client } -describe('KeyExchangeUtil', () => { +describe.skip('KeyExchangeUtil', () => { let client let util beforeEach(async () => { diff --git a/test/unit/KeyHistoryStorageUtil.test.js b/test/unit/KeyHistoryStorageUtil.test.js index 3195e4b5f..a493117c2 100644 --- a/test/unit/KeyHistoryStorageUtil.test.js +++ b/test/unit/KeyHistoryStorageUtil.test.js @@ -2,7 +2,7 @@ import crypto from 'crypto' import KeyStorageUtil from '../../src/KeyStorageUtil' -describe('KeyHistoryStorageUtil', () => { +describe.skip('KeyHistoryStorageUtil', () => { describe('hasKey()', () => { it('returns true iff there is a GroupKeyHistory for the stream', () => { const util = KeyStorageUtil.getKeyStorageUtil({ diff --git a/test/unit/LatestKeyStorageUtil.test.js b/test/unit/LatestKeyStorageUtil.test.js index 5eeeedd9e..6af172947 100644 --- a/test/unit/LatestKeyStorageUtil.test.js +++ b/test/unit/LatestKeyStorageUtil.test.js @@ -2,7 +2,7 @@ import crypto from 'crypto' import KeyStorageUtil from '../../src/KeyStorageUtil' -describe('LatestKeyStorageUtil', () => { +describe.skip('LatestKeyStorageUtil', () => { describe('hasKey()', () => { it('returns true iff there is a GroupKeyHistory for the stream', () => { const util = KeyStorageUtil.getKeyStorageUtil({ diff --git a/test/unit/MessageCreationUtil.test.js b/test/unit/MessageCreationUtil.test.js index 931a2789e..e0f5f2b6b 100644 --- a/test/unit/MessageCreationUtil.test.js +++ b/test/unit/MessageCreationUtil.test.js @@ -234,7 +234,7 @@ describe('MessageCreationUtil', () => { }) }) - describe('encryption', () => { + describe.skip('encryption', () => { const pubMsg = { foo: 'bar', } @@ -326,7 +326,7 @@ describe('MessageCreationUtil', () => { }) }) - describe('createGroupKeyRequest', () => { + describe.skip('createGroupKeyRequest', () => { const stream = new Stream(null, { id: 'streamId', partitions: 1, @@ -388,7 +388,7 @@ describe('MessageCreationUtil', () => { }) }) - describe('createGroupKeyResponse', () => { + describe.skip('createGroupKeyResponse', () => { const stream = new Stream(null, { id: 'streamId', partitions: 1, @@ -417,7 +417,7 @@ describe('MessageCreationUtil', () => { }) }) - it('creates correct group key response', async () => { + it.skip('creates correct group key response', async () => { const signer = { signStreamMessage: (streamMessage) => { /* eslint-disable no-param-reassign */ @@ -457,7 +457,7 @@ describe('MessageCreationUtil', () => { }) }) - describe('createErrorMessage', () => { + describe.skip('createErrorMessage', () => { const stream = new Stream(null, { id: 'streamId', partitions: 1, diff --git a/test/unit/RealTimeSubscription.test.js b/test/unit/RealTimeSubscription.test.js index 8f7f5c727..c77972266 100644 --- a/test/unit/RealTimeSubscription.test.js +++ b/test/unit/RealTimeSubscription.test.js @@ -1,6 +1,7 @@ import crypto from 'crypto' import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' +import { wait } from 'streamr-test-utils' import RealTimeSubscription from '../../src/RealTimeSubscription' import EncryptionUtil from '../../src/EncryptionUtil' @@ -34,11 +35,15 @@ describe('RealTimeSubscription', () => { it('calls the message handler', async (done) => { const handler = jest.fn(async () => true) - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), (content, receivedMsg) => { - expect(content).toStrictEqual(msg.getParsedContent()) - expect(msg).toStrictEqual(receivedMsg) - expect(handler).toHaveBeenCalledTimes(1) - done() + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: (content, receivedMsg) => { + expect(content).toStrictEqual(msg.getParsedContent()) + expect(msg).toStrictEqual(receivedMsg) + expect(handler).toHaveBeenCalledTimes(1) + done() + }, }) await sub.handleBroadcastMessage(msg, handler) }) @@ -47,7 +52,11 @@ describe('RealTimeSubscription', () => { let sub beforeEach(() => { - sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => { throw new Error('should not be called!') }) + sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => { throw new Error('should not be called!') }, + }) sub.onError = jest.fn() }) @@ -95,11 +104,15 @@ describe('RealTimeSubscription', () => { const received = [] - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), (content, receivedMsg) => { - received.push(receivedMsg) - if (received.length === 5) { - expect(msgs).toStrictEqual(received) - done() + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: (content, receivedMsg) => { + received.push(receivedMsg) + if (received.length === 5) { + expect(msgs).toStrictEqual(received) + done() + } } }) @@ -110,7 +123,11 @@ describe('RealTimeSubscription', () => { describe('handleResentMessage()', () => { it('processes messages if resending is true', async () => { const handler = jest.fn() - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), handler) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: handler, + }) sub.setResending(true) await sub.handleResentMessage(msg, 'requestId', async () => true) @@ -121,8 +138,12 @@ describe('RealTimeSubscription', () => { let sub beforeEach(() => { - sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => { - throw new Error('should not be called!') + sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => { + throw new Error('should not be called!') + }, }) sub.setResending(true) }) @@ -171,7 +192,11 @@ describe('RealTimeSubscription', () => { describe('duplicate handling', () => { it('ignores re-received messages', async () => { const handler = jest.fn() - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), handler) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: handler, + }) await sub.handleBroadcastMessage(msg, async () => true) await sub.handleBroadcastMessage(msg, async () => true) @@ -181,7 +206,11 @@ describe('RealTimeSubscription', () => { it('ignores re-received messages if they come from resend', async () => { const handler = jest.fn() - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), handler) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: handler, + }) sub.setResending(true) await sub.handleBroadcastMessage(msg, async () => true) @@ -194,8 +223,13 @@ describe('RealTimeSubscription', () => { it('emits "gap" if a gap is detected', (done) => { const msg1 = msg const msg4 = createMsg(4, undefined, 3) - - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}, {}, 100, 100) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + propagationTimeout: 100, + resendTimeout: 100, + }) sub.once('gap', (from, to, publisherId) => { expect(from.timestamp).toEqual(1) // cannot know the first missing message so there will be a duplicate received expect(from.sequenceNumber).toEqual(1) @@ -216,7 +250,14 @@ describe('RealTimeSubscription', () => { const msg1 = msg const msg4 = createMsg(4, undefined, 3) - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}, {}, 100, 100) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + propagationTimeout: 100, + resendTimeout: 100, + }) + sub.once('gap', (from, to, publisherId) => { sub.once('gap', (from2, to2, publisherId2) => { expect(from).toStrictEqual(from2) @@ -237,7 +278,14 @@ describe('RealTimeSubscription', () => { const msg3 = createMsg(3, undefined, 2) const msg4 = createMsg(4, undefined, 3) - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}, {}, 100, 100) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + propagationTimeout: 100, + resendTimeout: 100, + }) + sub.once('gap', () => { sub.handleBroadcastMessage(msg2, async () => true) sub.handleBroadcastMessage(msg3, async () => true) @@ -256,7 +304,14 @@ describe('RealTimeSubscription', () => { const msg1 = msg const msg4 = createMsg(4, undefined, 3) - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}, {}, 100, 100) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + propagationTimeout: 100, + resendTimeout: 100, + }) + sub.once('gap', () => { sub.emit('unsubscribed') sub.once('gap', () => { throw new Error('should not emit second gap') }) @@ -274,7 +329,14 @@ describe('RealTimeSubscription', () => { const msg1 = msg const msg4 = createMsg(4, undefined, 3) - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}, {}, 100, 100) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + propagationTimeout: 100, + resendTimeout: 100, + }) + sub.once('gap', () => { sub.emit('disconnected') sub.once('gap', () => { throw new Error('should not emit second gap') }) @@ -288,24 +350,37 @@ describe('RealTimeSubscription', () => { sub.handleBroadcastMessage(msg4, async () => true) }) - it('does not emit "gap" if different publishers', () => { + it('does not emit "gap" if different publishers', async () => { const msg1 = msg const msg1b = createMsg(1, 0, undefined, 0, {}, 'anotherPublisherId') - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + propagationTimeout: 100, + resendTimeout: 100, + }) sub.once('gap', () => { throw new Error('unexpected gap') }) sub.handleBroadcastMessage(msg1, async () => true) sub.handleBroadcastMessage(msg1b, async () => true) + await wait(100) }) it('emits "gap" if a gap is detected (same timestamp but different sequenceNumbers)', (done) => { const msg1 = msg const msg4 = createMsg(1, 4, 1, 3) - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}, {}, 100, 100) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + propagationTimeout: 100, + resendTimeout: 100, + }) sub.once('gap', (from, to, publisherId) => { expect(from.timestamp).toEqual(1) // cannot know the first missing message so there will be a duplicate received expect(from.sequenceNumber).toEqual(1) @@ -322,26 +397,39 @@ describe('RealTimeSubscription', () => { sub.handleBroadcastMessage(msg4, async () => true) }) - it('does not emit "gap" if a gap is not detected', () => { + it('does not emit "gap" if a gap is not detected', async () => { const msg1 = msg const msg2 = createMsg(2, undefined, 1) - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + propagationTimeout: 100, + resendTimeout: 100, + }) sub.once('gap', () => { throw new Error() }) sub.handleBroadcastMessage(msg1, async () => true) sub.handleBroadcastMessage(msg2, async () => true) }) - it('does not emit "gap" if a gap is not detected (same timestamp but different sequenceNumbers)', () => { + it('does not emit "gap" if a gap is not detected (same timestamp but different sequenceNumbers)', async () => { const msg1 = msg const msg2 = createMsg(1, 1, 1, 0) - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + propagationTimeout: 100, + resendTimeout: 100, + }) sub.once('gap', () => { throw new Error() }) sub.handleBroadcastMessage(msg1, async () => true) sub.handleBroadcastMessage(msg2, async () => true) + await wait(100) }) }) @@ -352,10 +440,16 @@ describe('RealTimeSubscription', () => { const msg3 = createMsg(3, 0, 2, 0) const msg4 = createMsg(4, 0, 3, 0) const received = [] - - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), (content, receivedMsg) => { - received.push(receivedMsg) - }, {}, 100, 100, false) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: (content, receivedMsg) => { + received.push(receivedMsg) + }, + propagationTimeout: 100, + resendTimeout: 100, + orderMessages: false, + }) sub.once('gap', () => { throw new Error() }) await sub.handleBroadcastMessage(msg1, async () => true) @@ -375,8 +469,15 @@ describe('RealTimeSubscription', () => { const msg4 = createMsg(4, 0, 3, 0) const received = [] - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), (content, receivedMsg) => { - received.push(receivedMsg) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: (content, receivedMsg) => { + received.push(receivedMsg) + }, + propagationTimeout: 100, + resendTimeout: 100, + orderMessages: true, }) await sub.handleBroadcastMessage(msg1, async () => true) @@ -395,7 +496,11 @@ describe('RealTimeSubscription', () => { _bye: true, }) const handler = jest.fn() - const sub = new RealTimeSubscription(byeMsg.getStreamId(), byeMsg.getStreamPartition(), handler) + const sub = new RealTimeSubscription({ + streamId: byeMsg.getStreamId(), + streamPartition: byeMsg.getStreamPartition(), + callback: handler, + }) sub.once('done', () => { expect(handler).toHaveBeenCalledTimes(1) done() @@ -414,9 +519,13 @@ describe('RealTimeSubscription', () => { const msg1 = createMsg(1, 0, null, 0, { foo: 'bar', }) - sub = new RealTimeSubscription(msg1.getStreamId(), msg1.getStreamPartition(), (content) => { - expect(content).toStrictEqual(msg1.getParsedContent()) - done() + sub = new RealTimeSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: (content) => { + expect(content).toStrictEqual(msg1.getParsedContent()) + done() + }, }) return sub.handleBroadcastMessage(msg1, async () => true) }) @@ -428,11 +537,16 @@ describe('RealTimeSubscription', () => { } const msg1 = createMsg(1, 0, null, 0, data) EncryptionUtil.encryptStreamMessage(msg1, groupKey) - sub = new RealTimeSubscription(msg1.getStreamId(), msg1.getStreamPartition(), (content) => { - expect(content).toStrictEqual(data) - done() - }, { - publisherId: groupKey, + sub = new RealTimeSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: (content) => { + expect(content).toStrictEqual(msg1.getParsedContent()) + done() + }, + groupKeys: { + publisherId: groupKey, + } }) return sub.handleBroadcastMessage(msg1, async () => true) }) @@ -444,8 +558,13 @@ describe('RealTimeSubscription', () => { foo: 'bar', }) EncryptionUtil.encryptStreamMessage(msg1, correctGroupKey) - sub = new RealTimeSubscription(msg1.getStreamId(), msg1.getStreamPartition(), () => {}, { - publisherId: wrongGroupKey, + sub = new RealTimeSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: () => {}, + groupKeys: { + publisherId: wrongGroupKey, + } }) sub.once('groupKeyMissing', (publisherId) => { expect(publisherId).toBe(msg1.getPublisherId()) @@ -462,9 +581,15 @@ describe('RealTimeSubscription', () => { foo: 'bar', }) EncryptionUtil.encryptStreamMessage(msg1, correctGroupKey) - sub = new RealTimeSubscription(msg1.getStreamId(), msg1.getStreamPartition(), () => {}, { - publisherId: wrongGroupKey, - }, 200) + sub = new RealTimeSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: () => {}, + groupKeys: { + publisherId: wrongGroupKey, + }, + propagationTimeout: 200, + }) sub.on('groupKeyMissing', (publisherId) => { if (counter < 3) { expect(publisherId).toBe(msg1.getPublisherId()) @@ -492,9 +617,15 @@ describe('RealTimeSubscription', () => { }) const timeout = 200 EncryptionUtil.encryptStreamMessage(msg1, correctGroupKey) - sub = new RealTimeSubscription(msg1.getStreamId(), msg1.getStreamPartition(), () => {}, { - publisherId: wrongGroupKey, - }, timeout) + sub = new RealTimeSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: () => {}, + groupKeys: { + publisherId: wrongGroupKey, + }, + propagationTimeout: timeout, + }) let t sub.on('groupKeyMissing', (publisherId) => { expect(publisherId).toBe(msg1.getPublisherId()) @@ -523,14 +654,19 @@ describe('RealTimeSubscription', () => { EncryptionUtil.encryptStreamMessage(msg2, correctGroupKey) let received1 = null let received2 = null - sub = new RealTimeSubscription(msg1.getStreamId(), msg1.getStreamPartition(), (content) => { - if (!received1) { - received1 = content - } else { - received2 = content - } - }, { - publisherId: wrongGroupKey, + sub = new RealTimeSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: (content) => { + if (!received1) { + received1 = content + } else { + received2 = content + } + }, + groupKeys: { + publisherId: wrongGroupKey, + }, }) // cannot decrypt msg1, queues it and emits "groupKeyMissing" (should send group key request). await sub.handleBroadcastMessage(msg1, async () => true) @@ -568,10 +704,15 @@ describe('RealTimeSubscription', () => { EncryptionUtil.encryptStreamMessage(msg3, groupKey2) EncryptionUtil.encryptStreamMessage(msg4, groupKey2) const received = [] - sub = new RealTimeSubscription(msg1.getStreamId(), msg1.getStreamPartition(), (content) => { - received.push(content) - }, { - publisherId1: wrongGroupKey, + sub = new RealTimeSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: (content) => { + received.push(content) + }, + groupKeys: { + publisherId: wrongGroupKey, + }, }) // cannot decrypt msg1, queues it and emits "groupKeyMissing" (should send group key request). await sub.handleBroadcastMessage(msg1, async () => true) @@ -620,10 +761,15 @@ describe('RealTimeSubscription', () => { EncryptionUtil.encryptStreamMessage(msg1Pub2, groupKey2) EncryptionUtil.encryptStreamMessage(msg2Pub2, groupKey2) const received = [] - sub = new RealTimeSubscription(msg1Pub1.getStreamId(), msg1Pub1.getStreamPartition(), (content) => { - received.push(content) - }, { - publisherId1: wrongGroupKey, + sub = new RealTimeSubscription({ + streamId: msg1Pub1.getStreamId(), + streamPartition: msg1Pub1.getStreamPartition(), + callback: (content) => { + received.push(content) + }, + groupKeys: { + publisherId: wrongGroupKey, + }, }) await sub.handleBroadcastMessage(msg1Pub1, async () => true) await sub.handleBroadcastMessage(msg1Pub2, async () => true) @@ -654,12 +800,18 @@ describe('RealTimeSubscription', () => { EncryptionUtil.encryptStreamMessage(msg1, correctGroupKey) EncryptionUtil.encryptStreamMessage(msg2, correctGroupKey) let undecryptableMsg = null - sub = new RealTimeSubscription(msg1.getStreamId(), msg1.getStreamPartition(), () => { - throw new Error('should not call the handler') - }, { - publisherId: wrongGroupKey, - }, 5000, 5000, true, (error) => { - undecryptableMsg = error.streamMessage + sub = new RealTimeSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: () => { + throw new Error('should not call the handler') + }, + groupKeys: { + publisherId: wrongGroupKey, + }, + onUnableToDecrypt: (error) => { + undecryptableMsg = error.streamMessage + } }) // cannot decrypt msg1, emits "groupKeyMissing" (should send group key request). await sub.handleBroadcastMessage(msg1, async () => true) @@ -684,15 +836,20 @@ describe('RealTimeSubscription', () => { EncryptionUtil.encryptStreamMessageAndNewKey(groupKey2, msg1, groupKey1) EncryptionUtil.encryptStreamMessage(msg2, groupKey2) let test1Ok = false - sub = new RealTimeSubscription(msg1.getStreamId(), msg1.getStreamPartition(), (content) => { - if (JSON.stringify(content) === JSON.stringify(data1)) { - expect(sub.groupKeys[msg1.getPublisherId().toLowerCase()]).toStrictEqual(groupKey2) - test1Ok = true - } else if (test1Ok && JSON.stringify(content) === JSON.stringify(data2)) { - done() - } - }, { - publisherId: groupKey1, + sub = new RealTimeSubscription({ + streamId: msg1.getStreamId(), + streamPartition: msg1.getStreamPartition(), + callback: (content) => { + if (JSON.stringify(content) === JSON.stringify(data1)) { + expect(sub.groupKeys[msg1.getPublisherId().toLowerCase()]).toStrictEqual(groupKey2) + test1Ok = true + } else if (test1Ok && JSON.stringify(content) === JSON.stringify(data2)) { + done() + } + }, + groupKeys: { + publisherId: groupKey1, + }, }) await sub.handleBroadcastMessage(msg1, async () => true) return sub.handleBroadcastMessage(msg2, async () => true) @@ -703,11 +860,13 @@ describe('RealTimeSubscription', () => { describe('handleError()', () => { it('emits an error event', (done) => { const err = new Error('Test error') - const sub = new RealTimeSubscription( - msg.getStreamId(), - msg.getStreamPartition(), - () => { throw new Error('Msg handler should not be called!') }, - ) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => { + throw new Error('Msg handler should not be called!') + }, + }) sub.onError = jest.fn() sub.once('error', (thrown) => { expect(err === thrown).toBeTruthy() @@ -717,11 +876,15 @@ describe('RealTimeSubscription', () => { }) it('marks the message as received if an InvalidJsonError occurs, and continue normally on next message', async (done) => { - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), (content, receivedMsg) => { - if (receivedMsg.getTimestamp() === 3) { - sub.stop() - done() - } + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: (content, receivedMsg) => { + if (receivedMsg.getTimestamp() === 3) { + sub.stop() + done() + } + }, }) sub.onError = jest.fn() @@ -742,7 +905,13 @@ describe('RealTimeSubscription', () => { }) it('if an InvalidJsonError AND a gap occur, does not mark it as received and emits gap at the next message', async (done) => { - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}, {}, 100, 100) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + propagationTimeout: 100, + resendTimeout: 100, + }) sub.onError = jest.fn() sub.once('gap', (from, to, publisherId) => { @@ -774,12 +943,20 @@ describe('RealTimeSubscription', () => { describe('setState()', () => { it('updates the state', () => { - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + }) sub.setState(Subscription.State.subscribed) expect(sub.getState()).toEqual(Subscription.State.subscribed) }) it('fires an event', (done) => { - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + }) sub.once(Subscription.State.subscribed, done) sub.setState(Subscription.State.subscribed) }) @@ -787,7 +964,11 @@ describe('RealTimeSubscription', () => { describe('handleResending()', () => { it('emits the resending event', (done) => { - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + }) sub.addPendingResendRequestId('requestId') sub.once('resending', () => done()) sub.setResending(true) @@ -802,7 +983,11 @@ describe('RealTimeSubscription', () => { describe('handleResent()', () => { it('arms the Subscription to emit the resent event on last message (message handler completes BEFORE resent)', async (done) => { const handler = jest.fn() - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), handler) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: handler, + }) sub.addPendingResendRequestId('requestId') sub.once('resent', () => { expect(handler).toHaveBeenCalledTimes(1) @@ -819,7 +1004,11 @@ describe('RealTimeSubscription', () => { it('arms the Subscription to emit the resent event on last message (message handler completes AFTER resent)', async (done) => { const handler = jest.fn() - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), handler) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: handler, + }) sub.addPendingResendRequestId('requestId') sub.once('resent', () => { expect(handler).toHaveBeenCalledTimes(1) @@ -842,7 +1031,11 @@ describe('RealTimeSubscription', () => { }) it('cleans up the resend if event handler throws', async () => { - sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}) + sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + }) sub.onError = jest.fn() const error = new Error('test error, ignore') sub.addPendingResendRequestId('requestId') @@ -862,7 +1055,11 @@ describe('RealTimeSubscription', () => { describe('handleNoResend()', () => { it('emits the no_resend event', async () => { - const sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}) + const sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + }) sub.addPendingResendRequestId('requestId') const onNoResent = new Promise((resolve) => sub.once('no_resend', resolve)) sub.setResending(true) @@ -883,7 +1080,11 @@ describe('RealTimeSubscription', () => { }) it('cleans up the resend if event handler throws', async () => { - sub = new RealTimeSubscription(msg.getStreamId(), msg.getStreamPartition(), () => {}) + sub = new RealTimeSubscription({ + streamId: msg.getStreamId(), + streamPartition: msg.getStreamPartition(), + callback: () => {}, + }) sub.onError = jest.fn() const error = new Error('test error, ignore') sub.addPendingResendRequestId('requestId') diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index 851f30f6a..a36997307 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -966,7 +966,7 @@ describe('StreamrClient', () => { }) }) - it('sets the group keys if passed as arguments', () => { + it.skip('sets the group keys if passed as arguments', () => { const groupKey = crypto.randomBytes(32) const sub = client.subscribe({ stream: 'stream1', @@ -1540,78 +1540,80 @@ describe('StreamrClient', () => { expect(c.session.options.unauthenticated).toBeTruthy() }) - it('sets start time of group key', () => { - const groupKey = crypto.randomBytes(32) - const c = new StubbedStreamrClient({ - subscriberGroupKeys: { - streamId: { - publisherId: groupKey + describe.skip('groupKeys', () => { + it('sets start time of group key', () => { + const groupKey = crypto.randomBytes(32) + const c = new StubbedStreamrClient({ + subscriberGroupKeys: { + streamId: { + publisherId: groupKey + } } - } - }, createConnectionMock()) - expect(c.options.subscriberGroupKeys.streamId.publisherId.groupKey).toBe(groupKey) - expect(c.options.subscriberGroupKeys.streamId.publisherId.start).toBeTruthy() - }) + }, createConnectionMock()) + expect(c.options.subscriberGroupKeys.streamId.publisherId.groupKey).toBe(groupKey) + expect(c.options.subscriberGroupKeys.streamId.publisherId.start).toBeTruthy() + }) - it('keeps start time passed in the constructor', () => { - const groupKey = crypto.randomBytes(32) - const c = new StubbedStreamrClient({ - subscriberGroupKeys: { - streamId: { - publisherId: { - groupKey, - start: 12 + it('keeps start time passed in the constructor', () => { + const groupKey = crypto.randomBytes(32) + const c = new StubbedStreamrClient({ + subscriberGroupKeys: { + streamId: { + publisherId: { + groupKey, + start: 12 + } } } - } - }, createConnectionMock()) - expect(c.options.subscriberGroupKeys.streamId.publisherId.groupKey).toBe(groupKey) - expect(c.options.subscriberGroupKeys.streamId.publisherId.start).toBe(12) - }) + }, createConnectionMock()) + expect(c.options.subscriberGroupKeys.streamId.publisherId.groupKey).toBe(groupKey) + expect(c.options.subscriberGroupKeys.streamId.publisherId.start).toBe(12) + }) - it('updates the latest group key with a more recent key', () => { - const c = new StubbedStreamrClient({ - subscriberGroupKeys: { - streamId: { - publisherId: crypto.randomBytes(32) + it('updates the latest group key with a more recent key', () => { + const c = new StubbedStreamrClient({ + subscriberGroupKeys: { + streamId: { + publisherId: crypto.randomBytes(32) + } + } + }, createConnectionMock()) + c.subscribedStreamPartitions = { + streamId0: { + setSubscriptionsGroupKeys: sinon.stub() } } - }, createConnectionMock()) - c.subscribedStreamPartitions = { - streamId0: { - setSubscriptionsGroupKeys: sinon.stub() + const newGroupKey = { + groupKey: crypto.randomBytes(32), + start: Date.now() + 2000 } - } - const newGroupKey = { - groupKey: crypto.randomBytes(32), - start: Date.now() + 2000 - } - // eslint-disable-next-line no-underscore-dangle - c._setGroupKeys('streamId', 'publisherId', [newGroupKey]) - expect(c.options.subscriberGroupKeys.streamId.publisherId).toBe(newGroupKey) - }) + // eslint-disable-next-line no-underscore-dangle + c._setGroupKeys('streamId', 'publisherId', [newGroupKey]) + expect(c.options.subscriberGroupKeys.streamId.publisherId).toBe(newGroupKey) + }) - it('does not update the latest group key with an older key', () => { - const groupKey = crypto.randomBytes(32) - const c = new StubbedStreamrClient({ - subscriberGroupKeys: { - streamId: { - publisherId: groupKey + it('does not update the latest group key with an older key', () => { + const groupKey = crypto.randomBytes(32) + const c = new StubbedStreamrClient({ + subscriberGroupKeys: { + streamId: { + publisherId: groupKey + } + } + }, createConnectionMock()) + c.subscribedStreamPartitions = { + streamId0: { + setSubscriptionsGroupKeys: sinon.stub() } } - }, createConnectionMock()) - c.subscribedStreamPartitions = { - streamId0: { - setSubscriptionsGroupKeys: sinon.stub() + const oldGroupKey = { + groupKey: crypto.randomBytes(32), + start: Date.now() - 2000 } - } - const oldGroupKey = { - groupKey: crypto.randomBytes(32), - start: Date.now() - 2000 - } - // eslint-disable-next-line no-underscore-dangle - c._setGroupKeys('streamId', 'publisherId', [oldGroupKey]) - expect(c.options.subscriberGroupKeys.streamId.publisherId.groupKey).toBe(groupKey) + // eslint-disable-next-line no-underscore-dangle + c._setGroupKeys('streamId', 'publisherId', [oldGroupKey]) + expect(c.options.subscriberGroupKeys.streamId.publisherId.groupKey).toBe(groupKey) + }) }) }) diff --git a/test/unit/SubscribedStreamPartition.test.js b/test/unit/SubscribedStreamPartition.test.js index 162657dd6..1c0bb416a 100644 --- a/test/unit/SubscribedStreamPartition.test.js +++ b/test/unit/SubscribedStreamPartition.test.js @@ -284,7 +284,10 @@ describe('SubscribedStreamPartition', () => { beforeEach(() => { ({ client } = setupClientAndStream()) subscribedStreamPartition = new SubscribedStreamPartition(client, 'streamId') - sub1 = new RealTimeSubscription('sub1Id', 0, () => {}) + sub1 = new RealTimeSubscription({ + streamId: 'sub1Id', + callback: () => {}, + }) }) it('should add and remove subscription correctly', () => { @@ -308,7 +311,7 @@ describe('SubscribedStreamPartition', () => { expect(subscribedStreamPartition.emptySubscriptionsSet()).toBe(true) }) - it('should call setGroupKeys() and checkQueue() for every subscription', async () => { + it.skip('should call setGroupKeys() and checkQueue() for every subscription', async () => { const sub2 = { id: 'sub2Id', setGroupKeys: sinon.stub(), From 189b5325de5ca440b3323f918d428482ea65af68 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 10 Aug 2020 10:39:38 -0400 Subject: [PATCH 002/517] Skip decryption integration tests. --- test/integration/StreamrClient.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index d226540c2..14044f8ff 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -956,7 +956,7 @@ describe('StreamrClient', () => { it('decodes realtime messages correctly', async (done) => { client.once('error', done) - const sub = client.subscribe(stream.id, (msg) => { + client.subscribe(stream.id, (msg) => { expect(msg).toStrictEqual(publishedMessage) done() }).once('subscribed', () => { @@ -980,7 +980,7 @@ describe('StreamrClient', () => { }, 10000) }) - describe('decryption', () => { + describe.skip('decryption', () => { it('client.subscribe can decrypt encrypted messages if it knows the group key', async (done) => { client.once('error', done) const id = Date.now() From a2ac41d484c69f2fd3825ca30b6a58b20c3088cb Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 6 Aug 2020 13:29:00 -0400 Subject: [PATCH 003/517] Don't mutate options when validating subscribe options. --- src/StreamrClient.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 36d03b71e..7a28806bd 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -513,7 +513,10 @@ export default class StreamrClient extends EventEmitter { stream: optionsOrStreamId, } } else if (typeof optionsOrStreamId === 'object') { - options = optionsOrStreamId + // shallow copy + options = { + ...optionsOrStreamId + } } else { throw new Error(`subscribe/resend: options must be an object! Given: ${optionsOrStreamId}`) } From e67817a075bf2e2854a4a5101e5de5909e8bd2f7 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 10 Aug 2020 12:10:33 -0400 Subject: [PATCH 004/517] Remove all groupkey/encryption handling in preparation for readding it in a more centralised manner. --- src/AbstractSubscription.js | 107 +----- src/CombinedSubscription.js | 13 - src/DecryptionKeySequence.js | 36 -- src/EncryptionUtil.js | 241 -------------- src/GroupKeyHistory.js | 53 --- src/HistoricalSubscription.js | 44 +-- src/KeyExchangeUtil.js | 118 ------- src/KeyHistoryStorageUtil.js | 35 -- src/KeyStorageUtil.js | 42 --- src/LatestKeyStorageUtil.js | 30 -- src/MessageCreationUtil.js | 113 +------ src/RealTimeSubscription.js | 37 --- src/StreamrClient.js | 122 +------ src/SubscribedStreamPartition.js | 6 - src/Subscription.js | 7 - test/integration/StreamrClient.test.js | 127 +------ test/unit/EncryptionUtil.test.js | 226 ------------- test/unit/HistoricalSubscription.test.js | 206 ------------ test/unit/KeyExchangeUtil.test.js | 325 ------------------ test/unit/KeyHistoryStorageUtil.test.js | 122 ------- test/unit/LatestKeyStorageUtil.test.js | 72 ---- test/unit/MessageCreationUtil.test.js | 292 ---------------- test/unit/RealTimeSubscription.test.js | 350 -------------------- test/unit/StreamrClient.test.js | 97 +----- test/unit/SubscribedStreamPartition.test.js | 19 -- 25 files changed, 17 insertions(+), 2823 deletions(-) delete mode 100644 src/DecryptionKeySequence.js delete mode 100644 src/EncryptionUtil.js delete mode 100644 src/GroupKeyHistory.js delete mode 100644 src/KeyExchangeUtil.js delete mode 100644 src/KeyHistoryStorageUtil.js delete mode 100644 src/KeyStorageUtil.js delete mode 100644 src/LatestKeyStorageUtil.js delete mode 100644 test/unit/EncryptionUtil.test.js delete mode 100644 test/unit/KeyExchangeUtil.test.js delete mode 100644 test/unit/KeyHistoryStorageUtil.test.js delete mode 100644 test/unit/LatestKeyStorageUtil.test.js diff --git a/src/AbstractSubscription.js b/src/AbstractSubscription.js index cd6870157..e201f122c 100644 --- a/src/AbstractSubscription.js +++ b/src/AbstractSubscription.js @@ -1,24 +1,14 @@ import { Errors, Utils } from 'streamr-client-protocol' import Subscription from './Subscription' -import UnableToDecryptError from './errors/UnableToDecryptError' const { OrderingUtil } = Utils -const MAX_NB_GROUP_KEY_REQUESTS = 10 - -function decryptErrorToDisplay(error) { - const ciphertext = error.streamMessage.getSerializedContent() - return ciphertext.length > 100 ? `${ciphertext.slice(0, 100)}...` : ciphertext -} - export default class AbstractSubscription extends Subscription { constructor({ streamId, streamPartition, callback, - groupKeys, - onUnableToDecrypt, propagationTimeout, resendTimeout, orderMessages = true, @@ -28,7 +18,6 @@ export default class AbstractSubscription extends Subscription { streamId, streamPartition, callback, - groupKeys, propagationTimeout, resendTimeout, debug, @@ -36,10 +25,6 @@ export default class AbstractSubscription extends Subscription { this.callback = callback this.pendingResendRequestIds = {} this._lastMessageHandlerPromise = {} - if (onUnableToDecrypt) { - this.onUnableToDecrypt = onUnableToDecrypt - } - this.onUnableToDecrypt = this.onUnableToDecrypt.bind(this) this.orderingUtil = (orderMessages) ? new OrderingUtil(streamId, streamPartition, (orderedMessage) => { this._inOrderHandler(orderedMessage) }, (from, to, publisherId, msgChainId) => { @@ -63,10 +48,6 @@ export default class AbstractSubscription extends Subscription { this._clearGaps() this.onError(error) }) - - this.encryptedMsgsQueues = {} - this.waitingForGroupKey = {} - this.nbGroupKeyRequests = {} } /** @@ -77,91 +58,13 @@ export default class AbstractSubscription extends Subscription { console.error(error) } - // eslint-disable-next-line class-methods-use-this - onUnableToDecrypt(error) { - this.debug(`WARN: Unable to decrypt: ${decryptErrorToDisplay(error)}`) - } - - _addMsgToQueue(encryptedMsg) { - const publisherId = encryptedMsg.getPublisherId().toLowerCase() - if (!this.encryptedMsgsQueues[publisherId]) { - this.encryptedMsgsQueues[publisherId] = [] - } - this.encryptedMsgsQueues[publisherId].push(encryptedMsg) - } - - _emptyMsgQueues() { - const queues = Object.values(this.encryptedMsgsQueues) - for (let i = 0; i < queues.length; i++) { - if (queues[i].length > 0) { - return false - } - } - return true - } - _inOrderHandler(orderedMessage) { - return this._catchAndEmitErrors(() => { - if (!this.waitingForGroupKey[orderedMessage.getPublisherId().toLowerCase()]) { - this._decryptAndHandle(orderedMessage) - } else { - this._addMsgToQueue(orderedMessage) - } - }) - } - - _decryptAndHandle(orderedMessage) { - let success - try { - success = this._decryptOrRequestGroupKey(orderedMessage, orderedMessage.getPublisherId().toLowerCase()) - } catch (err) { - if (err instanceof UnableToDecryptError) { - this.onUnableToDecrypt(err) - } else { - throw err - } - } - if (success) { - this.callback(orderedMessage.getParsedContent(), orderedMessage) - if (orderedMessage.isByeMessage()) { - this.emit('done') - } - } else { - this.debug('Failed to decrypt. Requested the correct decryption key(s) and going to try again.') - } - } - - _requestGroupKeyAndQueueMessage(msg, start, end) { - this.emit('groupKeyMissing', msg.getPublisherId(), start, end) - const publisherId = msg.getPublisherId().toLowerCase() - this.nbGroupKeyRequests[publisherId] = 1 // reset retry counter - clearInterval(this.waitingForGroupKey[publisherId]) - this.waitingForGroupKey[publisherId] = setInterval(() => { - if (this.nbGroupKeyRequests[publisherId] < MAX_NB_GROUP_KEY_REQUESTS) { - this.nbGroupKeyRequests[publisherId] += 1 - this.emit('groupKeyMissing', msg.getPublisherId(), start, end) - } else { - this.debug(`WARN: Failed to receive group key response from ${publisherId} after ${MAX_NB_GROUP_KEY_REQUESTS} requests.`) - this._cancelGroupKeyRequest(publisherId) - } - }, this.propagationTimeout) - this._addMsgToQueue(msg) - } - - _handleEncryptedQueuedMsgs(publisherId) { - this._cancelGroupKeyRequest(publisherId.toLowerCase()) - const queue = this.encryptedMsgsQueues[publisherId.toLowerCase()] - while (queue.length > 0) { - this._decryptAndHandle(queue.shift()) + this.callback(orderedMessage.getParsedContent(), orderedMessage) + if (orderedMessage.isByeMessage()) { + this.emit('done') } } - _cancelGroupKeyRequest(publisherId) { - clearInterval(this.waitingForGroupKey[publisherId]) - this.waitingForGroupKey[publisherId] = undefined - delete this.waitingForGroupKey[publisherId] - } - addPendingResendRequestId(requestId) { this.pendingResendRequestIds[requestId] = true } @@ -231,7 +134,6 @@ export default class AbstractSubscription extends Subscription { if (this.orderingUtil) { this.orderingUtil.clearGaps() } - Object.keys(this.waitingForGroupKey).forEach((publisherId) => this._cancelGroupKeyRequest(publisherId)) } stop() { @@ -300,6 +202,3 @@ export default class AbstractSubscription extends Subscription { } } } - -AbstractSubscription.defaultUnableToDecrypt = AbstractSubscription.prototype.defaultUnableToDecrypt -AbstractSubscription.MAX_NB_GROUP_KEY_REQUESTS = MAX_NB_GROUP_KEY_REQUESTS diff --git a/src/CombinedSubscription.js b/src/CombinedSubscription.js index f02fe45d1..10885fb2f 100644 --- a/src/CombinedSubscription.js +++ b/src/CombinedSubscription.js @@ -10,8 +10,6 @@ export default class CombinedSubscription extends Subscription { callback, options, propagationTimeout, - groupKeys, - onUnableToDecrypt, resendTimeout, orderMessages = true, debug, @@ -20,8 +18,6 @@ export default class CombinedSubscription extends Subscription { streamId, streamPartition, callback, - groupKeys, - onUnableToDecrypt, propagationTimeout, resendTimeout, debug, @@ -32,8 +28,6 @@ export default class CombinedSubscription extends Subscription { streamPartition, callback, options, - groupKeys, - onUnableToDecrypt, propagationTimeout: this.propagationTimeout, resendTimeout: this.resendTimeout, orderMessages, @@ -51,8 +45,6 @@ export default class CombinedSubscription extends Subscription { streamId, streamPartition, callback, - groupKeys, - onUnableToDecrypt, propagationTimeout: this.propagationTimeout, resendTimeout: this.resendTimeout, orderMessages, @@ -82,7 +74,6 @@ export default class CombinedSubscription extends Subscription { sub.on('no_resend', (response) => this.emit('no_resend', response)) sub.on('initial_resend_done', (response) => this.emit('initial_resend_done', response)) sub.on('message received', () => this.emit('message received')) - sub.on('groupKeyMissing', (publisherId, start, end) => this.emit('groupKeyMissing', publisherId, start, end)) // hack to ensure inner subscription state is reflected in the outer subscription state // restore in _unbindListeners @@ -157,10 +148,6 @@ export default class CombinedSubscription extends Subscription { super.setState(state) } - setGroupKeys(publisherId, groupKeys) { - this.sub.setGroupKeys(publisherId, groupKeys) - } - handleError(err) { return this.sub.handleError(err) } diff --git a/src/DecryptionKeySequence.js b/src/DecryptionKeySequence.js deleted file mode 100644 index 943545fb7..000000000 --- a/src/DecryptionKeySequence.js +++ /dev/null @@ -1,36 +0,0 @@ -import EncryptionUtil from './EncryptionUtil' -import UnableToDecryptError from './errors/UnableToDecryptError' - -export default class DecryptionKeySequence { - constructor(keys) { - this.keys = keys - this.currentIndex = 0 - } - - tryToDecryptResent(msg) { - try { - EncryptionUtil.decryptStreamMessage(msg, this.keys[this.currentIndex]) - } catch (err) { - // the current might not be valid anymore - if (err instanceof UnableToDecryptError) { - const nextKey = this._getNextKey() - if (!nextKey) { - throw err - } - // try to decrypt with the next key - EncryptionUtil.decryptStreamMessage(msg, nextKey) - // if successful (no error thrown) update the current key - this.currentIndex += 1 - } else { - throw err - } - } - } - - _getNextKey() { - if (this.currentIndex === this.keys.length - 1) { - return undefined - } - return this.keys[this.currentIndex + 1] - } -} diff --git a/src/EncryptionUtil.js b/src/EncryptionUtil.js deleted file mode 100644 index 95165adee..000000000 --- a/src/EncryptionUtil.js +++ /dev/null @@ -1,241 +0,0 @@ -import crypto from 'crypto' -import util from 'util' - -// this is shimmed out for actual browser build allows us to run tests in node against browser API -import { Crypto } from 'node-webcrypto-ossl' -import { ethers } from 'ethers' -import { MessageLayer } from 'streamr-client-protocol' - -import UnableToDecryptError from './errors/UnableToDecryptError' -import InvalidGroupKeyError from './errors/InvalidGroupKeyError' - -const { StreamMessage } = MessageLayer - -function ab2str(buf) { - return String.fromCharCode.apply(null, new Uint8Array(buf)) -} - -// shim browser btoa for node -function btoa(str) { - if (global.btoa) { return global.btoa(str) } - let buffer - - if (str instanceof Buffer) { - buffer = str - } else { - buffer = Buffer.from(str.toString(), 'binary') - } - - return buffer.toString('base64') -} - -async function exportCryptoKey(key, { isPrivate = false } = {}) { - const WebCrypto = new Crypto() - const keyType = isPrivate ? 'pkcs8' : 'spki' - const exported = await WebCrypto.subtle.exportKey(keyType, key) - const exportedAsString = ab2str(exported) - const exportedAsBase64 = btoa(exportedAsString) - const TYPE = isPrivate ? 'PRIVATE' : 'PUBLIC' - return `-----BEGIN ${TYPE} KEY-----\n${exportedAsBase64}\n-----END ${TYPE} KEY-----\n` -} - -export default class EncryptionUtil { - constructor(options = {}) { - if (options.privateKey && options.publicKey) { - EncryptionUtil.validatePrivateKey(options.privateKey) - EncryptionUtil.validatePublicKey(options.publicKey) - this.privateKey = options.privateKey - this.publicKey = options.publicKey - } else { - this._generateKeyPair() - } - } - - async onReady() { - if (this.isReady()) { return undefined } - return this._generateKeyPair() - } - - isReady() { - return !!this.privateKey - } - - // Returns a Buffer - decryptWithPrivateKey(ciphertext, isHexString = false) { - if (!this.isReady()) { throw new Error('EncryptionUtil not ready.') } - let ciphertextBuffer = ciphertext - if (isHexString) { - ciphertextBuffer = ethers.utils.arrayify(`0x${ciphertext}`) - } - return crypto.privateDecrypt(this.privateKey, ciphertextBuffer) - } - - // Returns a String (base64 encoding) - getPublicKey() { - if (!this.isReady()) { throw new Error('EncryptionUtil not ready.') } - return this.publicKey - } - - // Returns a Buffer or a hex String - static encryptWithPublicKey(plaintextBuffer, publicKey, hexlify = false) { - EncryptionUtil.validatePublicKey(publicKey) - const ciphertextBuffer = crypto.publicEncrypt(publicKey, plaintextBuffer) - if (hexlify) { - return ethers.utils.hexlify(ciphertextBuffer).slice(2) - } - return ciphertextBuffer - } - - /* - Both 'data' and 'groupKey' must be Buffers. Returns a hex string without the '0x' prefix. - */ - static encrypt(data, groupKey) { - const iv = crypto.randomBytes(16) // always need a fresh IV when using CTR mode - const cipher = crypto.createCipheriv('aes-256-ctr', groupKey, iv) - return ethers.utils.hexlify(iv).slice(2) + cipher.update(data, null, 'hex') + cipher.final('hex') - } - - /* - 'ciphertext' must be a hex string (without '0x' prefix), 'groupKey' must be a Buffer. Returns a Buffer. - */ - static decrypt(ciphertext, groupKey) { - const iv = ethers.utils.arrayify(`0x${ciphertext.slice(0, 32)}`) - const decipher = crypto.createDecipheriv('aes-256-ctr', groupKey, iv) - return Buffer.concat([decipher.update(ciphertext.slice(32), 'hex', null), decipher.final(null)]) - } - - /* - Sets the content of 'streamMessage' with the encryption result of the old content with 'groupKey'. - */ - static encryptStreamMessage(streamMessage, groupKey) { - /* eslint-disable no-param-reassign */ - streamMessage.encryptionType = StreamMessage.ENCRYPTION_TYPES.AES - streamMessage.serializedContent = this.encrypt(Buffer.from(streamMessage.getSerializedContent(), 'utf8'), groupKey) - streamMessage.parsedContent = undefined - /* eslint-enable no-param-reassign */ - } - - /* - Sets the content of 'streamMessage' with the encryption result of a plaintext with 'groupKey'. The - plaintext is the concatenation of 'newGroupKey' and the old serialized content of 'streamMessage'. - */ - static encryptStreamMessageAndNewKey(newGroupKey, streamMessage, groupKey) { - /* eslint-disable no-param-reassign */ - streamMessage.encryptionType = StreamMessage.ENCRYPTION_TYPES.NEW_KEY_AND_AES - const plaintext = Buffer.concat([newGroupKey, Buffer.from(streamMessage.getSerializedContent(), 'utf8')]) - streamMessage.serializedContent = EncryptionUtil.encrypt(plaintext, groupKey) - streamMessage.parsedContent = undefined - /* eslint-enable no-param-reassign */ - } - - /* - Decrypts the serialized content of 'streamMessage' with 'groupKey'. If the resulting plaintext is the concatenation - of a new group key and a message content, sets the content of 'streamMessage' with that message content and returns - the key. If the resulting plaintext is only a message content, sets the content of 'streamMessage' with that - message content and returns null. - */ - static decryptStreamMessage(streamMessage, groupKey) { - if ((streamMessage.encryptionType === StreamMessage.ENCRYPTION_TYPES.AES - || streamMessage.encryptionType === StreamMessage.ENCRYPTION_TYPES.NEW_KEY_AND_AES) && !groupKey) { - throw new UnableToDecryptError(streamMessage) - } - /* eslint-disable no-param-reassign */ - - if (streamMessage.encryptionType === StreamMessage.ENCRYPTION_TYPES.AES) { - streamMessage.encryptionType = StreamMessage.ENCRYPTION_TYPES.NONE - const serializedContent = this.decrypt(streamMessage.getSerializedContent(), groupKey).toString() - try { - streamMessage.parsedContent = JSON.parse(serializedContent) - streamMessage.serializedContent = serializedContent - } catch (err) { - streamMessage.encryptionType = StreamMessage.ENCRYPTION_TYPES.AES - throw new UnableToDecryptError(streamMessage) - } - } else if (streamMessage.encryptionType === StreamMessage.ENCRYPTION_TYPES.NEW_KEY_AND_AES) { - streamMessage.encryptionType = StreamMessage.ENCRYPTION_TYPES.NONE - const plaintext = this.decrypt(streamMessage.getSerializedContent(), groupKey) - const serializedContent = plaintext.slice(32).toString() - try { - streamMessage.parsedContent = JSON.parse(serializedContent) - streamMessage.serializedContent = serializedContent - } catch (err) { - streamMessage.encryptionType = StreamMessage.ENCRYPTION_TYPES.NEW_KEY_AND_AES - throw new UnableToDecryptError(streamMessage) - } - return plaintext.slice(0, 32) - } - return null - /* eslint-enable no-param-reassign */ - } - - async _generateKeyPair() { - if (!this._generateKeyPairPromise) { - this._generateKeyPairPromise = this.__generateKeyPair() - } - return this._generateKeyPairPromise - } - - async __generateKeyPair() { - if (process.browser) { return this._keyPairBrowser() } - return this._keyPairServer() - } - - async _keyPairServer() { - const generateKeyPair = util.promisify(crypto.generateKeyPair) - const { publicKey, privateKey } = await generateKeyPair('rsa', { - modulusLength: 4096, - publicKeyEncoding: { - type: 'spki', - format: 'pem', - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem', - }, - }) - - this.privateKey = privateKey - this.publicKey = publicKey - } - - async _keyPairBrowser() { - const WebCrypto = new Crypto() - const { publicKey, privateKey } = await WebCrypto.subtle.generateKey({ - name: 'RSA-OAEP', - modulusLength: 4096, - publicExponent: new Uint8Array([1, 0, 1]), // 65537 - hash: 'SHA-256' - }, true, ['encrypt', 'decrypt']) - - this.privateKey = await exportCryptoKey(privateKey, { - isPrivate: true, - }) - this.publicKey = await exportCryptoKey(publicKey, { - isPrivate: false, - }) - } - - static validatePublicKey(publicKey) { - if (typeof publicKey !== 'string' || !publicKey.startsWith('-----BEGIN PUBLIC KEY-----') - || !publicKey.endsWith('-----END PUBLIC KEY-----\n')) { - throw new Error('"publicKey" must be a PKCS#8 RSA public key as a string in the PEM format') - } - } - - static validatePrivateKey(privateKey) { - if (typeof privateKey !== 'string' || !privateKey.startsWith('-----BEGIN PRIVATE KEY-----') - || !privateKey.endsWith('-----END PRIVATE KEY-----\n')) { - throw new Error('"privateKey" must be a PKCS#8 RSA public key as a string in the PEM format') - } - } - - static validateGroupKey(groupKey) { - if (!(groupKey instanceof Buffer)) { - throw new InvalidGroupKeyError(`Group key must be a Buffer: ${groupKey}`) - } - - if (groupKey.length !== 32) { - throw new InvalidGroupKeyError(`Group key must have a size of 256 bits, not ${groupKey.length * 8}`) - } - } -} diff --git a/src/GroupKeyHistory.js b/src/GroupKeyHistory.js deleted file mode 100644 index 89ef46979..000000000 --- a/src/GroupKeyHistory.js +++ /dev/null @@ -1,53 +0,0 @@ -/* -This class contains the history of group keys used by the client as a publisher to encrypt messages for a particular stream. -The history is used to answer group key requests from subscribers who may ask for the latest key (getLatestKey() method) -in case of real-time messages or a sequence of historical keys (getKeysBetween() method) in case of resends. - */ -export default class GroupKeyHistory { - // initialGroupKey is an object with fields "groupKey" and "start" - constructor(initialGroupKey) { - this.keys = [] - if (initialGroupKey) { - this.keys.push(initialGroupKey) - } - } - - getLatestKey() { - return this.keys[this.keys.length - 1] - } - - getKeysBetween(start, end) { - if (typeof start !== 'number' || typeof end !== 'number' || start > end) { - throw new Error('Both "start" and "end" must be defined numbers and "start" must be less than or equal to "end".') - } - let i = 0 - // discard keys that ended before 'start' - while (i < this.keys.length - 1 && this._getKeyEnd(i) < start) { - i += 1 - } - const selectedKeys = [] - // add keys as long as they started before 'end' - while (i < this.keys.length && this.keys[i].start <= end) { - selectedKeys.push(this.keys[i]) - i += 1 - } - return selectedKeys - } - - addKey(groupKey, start) { - if (this.keys.length > 0 && this.keys[this.keys.length - 1].start > start) { - throw new Error(`Cannot add an older key to a group key history (${this.keys[this.keys.length - 1].start} > ${start})`) - } - this.keys.push({ - groupKey, - start: start || Date.now() - }) - } - - _getKeyEnd(keyIndex) { - if (keyIndex < 0 || keyIndex >= this.keys.length - 1) { - return undefined - } - return this.keys[keyIndex + 1].start - 1 - } -} diff --git a/src/HistoricalSubscription.js b/src/HistoricalSubscription.js index 542585d93..86efc5d16 100644 --- a/src/HistoricalSubscription.js +++ b/src/HistoricalSubscription.js @@ -1,17 +1,10 @@ -import { MessageLayer } from 'streamr-client-protocol' - import AbstractSubscription from './AbstractSubscription' -import DecryptionKeySequence from './DecryptionKeySequence' - -const { StreamMessage } = MessageLayer export default class HistoricalSubscription extends AbstractSubscription { constructor({ streamId, streamPartition, callback, - groupKeys, - onUnableToDecrypt = AbstractSubscription.defaultUnableToDecrypt, options, propagationTimeout, resendTimeout, @@ -22,8 +15,6 @@ export default class HistoricalSubscription extends AbstractSubscription { streamId, streamPartition, callback, - groupKeys, - onUnableToDecrypt, propagationTimeout, resendTimeout, orderMessages, @@ -45,26 +36,6 @@ export default class HistoricalSubscription extends AbstractSubscription { if (this.resendOptions.from == null && this.resendOptions.to != null) { throw new Error('"from" must be defined as well if "to" is defined.') } - this.keySequences = {} - Object.keys(this.groupKeys).forEach((publisherId) => { - this.keySequences[publisherId] = new DecryptionKeySequence([this.groupKeys[publisherId]]) - }) - } - - // passing publisherId separately to ensure it is lowercase (See call of this function in AbstractSubscription.js) - _decryptOrRequestGroupKey(msg, publisherId) { - if (msg.encryptionType !== StreamMessage.ENCRYPTION_TYPES.AES && msg.encryptionType !== StreamMessage.ENCRYPTION_TYPES.NEW_KEY_AND_AES) { - return true - } - - if (!this.keySequences[publisherId]) { - const start = msg.getTimestamp() - const end = this.resendOptions.to ? this.resendOptions.to : Date.now() - this._requestGroupKeyAndQueueMessage(msg, start, end) - return false - } - this.keySequences[publisherId].tryToDecryptResent(msg) - return true } async handleBroadcastMessage(msg, verifyFn) { @@ -88,22 +59,9 @@ export default class HistoricalSubscription extends AbstractSubscription { return this.resendOptions } - setGroupKeys(publisherId, groupKeys) { - if (this.keySequences[publisherId.toLowerCase()]) { - throw new Error(`Received historical group keys for publisher ${publisherId} for a second time.`) - } - this.keySequences[publisherId.toLowerCase()] = new DecryptionKeySequence(groupKeys) - this._handleEncryptedQueuedMsgs(publisherId) - if (this.resendDone && this._emptyMsgQueues()) { // the messages in the queue were the last ones to handle - this.emit('resend done') - } - } - finishResend() { this._lastMessageHandlerPromise = null - if (!this._emptyMsgQueues()) { // received all historical messages but not yet the keys to decrypt them - this.resendDone = true - } else if (Object.keys(this.pendingResendRequestIds).length === 0) { + if (Object.keys(this.pendingResendRequestIds).length === 0) { this.emit('initial_resend_done') } } diff --git a/src/KeyExchangeUtil.js b/src/KeyExchangeUtil.js deleted file mode 100644 index 6ce8ff948..000000000 --- a/src/KeyExchangeUtil.js +++ /dev/null @@ -1,118 +0,0 @@ -import uniqueId from 'lodash.uniqueid' - -import EncryptionUtil from './EncryptionUtil' -import InvalidGroupKeyRequestError from './errors/InvalidGroupKeyRequestError' -import InvalidGroupKeyResponseError from './errors/InvalidGroupKeyResponseError' -import InvalidGroupKeyError from './errors/InvalidGroupKeyError' - -const SUBSCRIBERS_EXPIRATION_TIME = 5 * 60 * 1000 // 5 minutes - -export default class KeyExchangeUtil { - static getKeyExchangeStreamId(publisherId) { - if (!publisherId || typeof publisherId !== 'string') { throw new Error(`non-empty publisherId string required: ${publisherId}`) } - return `SYSTEM/keyexchange/${publisherId.toLowerCase()}` - } - - constructor(client) { - this._client = client - this.debug = client.debug.extend(uniqueId('KeyExchangeUtil')) - this.isSubscriberPromises = {} - } - - async handleGroupKeyRequest(streamMessage) { - // No need to check if parsedContent contains the necessary fields because it was already checked during deserialization - const { streamId, range, requestId, publicKey } = streamMessage.getParsedContent() - let keys = [] - if (range) { - keys = this._client.keyStorageUtil.getKeysBetween(streamId, range.start, range.end) - } else { - const groupKeyObj = this._client.keyStorageUtil.getLatestKey(streamId, true) - if (groupKeyObj) { - keys.push(groupKeyObj) - } - } - - if (keys.length === 0) { - throw new InvalidGroupKeyRequestError(`Received group key request for stream '${streamId}' but no group key is set`) - } - const subscriberId = streamMessage.getPublisherId() - - const encryptedGroupKeys = [] - keys.forEach((keyObj) => { - const encryptedGroupKey = EncryptionUtil.encryptWithPublicKey(keyObj.groupKey, publicKey, true) - encryptedGroupKeys.push({ - groupKey: encryptedGroupKey, - start: keyObj.start, - }) - }) - const response = await this._client.msgCreationUtil.createGroupKeyResponse({ - subscriberAddress: subscriberId, - streamId, - encryptedGroupKeys, - requestId, - }) - return this._client.publishStreamMessage(response) - } - - handleGroupKeyResponse(streamMessage) { - // No need to check if parsedContent contains the necessary fields because it was already checked during deserialization - const parsedContent = streamMessage.getParsedContent() - // TODO: fix this hack in other PR - if (!this._client.subscribedStreamPartitions[parsedContent.streamId + '0']) { - throw new InvalidGroupKeyResponseError('Received group key response for a stream to which the client is not subscribed.') - } - - if (!this._client.encryptionUtil) { - throw new InvalidGroupKeyResponseError('Cannot decrypt group key response without the private key.') - } - - const decryptedGroupKeys = [] - parsedContent.keys.forEach((encryptedGroupKeyObj) => { - const groupKey = this._client.encryptionUtil.decryptWithPrivateKey(encryptedGroupKeyObj.groupKey, true) - try { - EncryptionUtil.validateGroupKey(groupKey) - } catch (err) { - if (err instanceof InvalidGroupKeyError) { - throw new InvalidGroupKeyResponseError(err.message) - } else { - throw err - } - } - decryptedGroupKeys.push({ - groupKey, - start: encryptedGroupKeyObj.start - }) - }) - /* eslint-disable no-underscore-dangle */ - this._client._setGroupKeys(parsedContent.streamId, streamMessage.getPublisherId(), decryptedGroupKeys) - /* eslint-enable no-underscore-dangle */ - this.debug('INFO: Updated group key for stream "%s" and publisher "%s"', parsedContent.streamId, streamMessage.getPublisherId()) - } - - async getSubscribers(streamId) { - if (!this.subscribersPromise || (Date.now() - this.lastAccess) > SUBSCRIBERS_EXPIRATION_TIME) { - this.subscribersPromise = this._client.getStreamSubscribers(streamId).then((subscribers) => { - const map = {} - subscribers.forEach((s) => { - map[s] = true - }) - return map - }) - this.lastAccess = Date.now() - } - return this.subscribersPromise - } - - async isSubscriber(streamId, subscriberId) { - if (!this.isSubscriberPromises[streamId]) { - this.isSubscriberPromises[streamId] = {} - } - - if (!this.isSubscriberPromises[streamId][subscriberId]) { - this.isSubscriberPromises[streamId][subscriberId] = this._client.isStreamSubscriber(streamId, subscriberId) - } - return this.isSubscriberPromises[streamId][subscriberId] - } -} - -KeyExchangeUtil.SUBSCRIBERS_EXPIRATION_TIME = SUBSCRIBERS_EXPIRATION_TIME diff --git a/src/KeyHistoryStorageUtil.js b/src/KeyHistoryStorageUtil.js deleted file mode 100644 index 8d4af8a39..000000000 --- a/src/KeyHistoryStorageUtil.js +++ /dev/null @@ -1,35 +0,0 @@ -import GroupKeyHistory from './GroupKeyHistory' - -export default class KeyHistoryStorageUtil { - constructor(publisherGroupKeys = {}) { - this.groupKeyHistories = {} - Object.keys(publisherGroupKeys).forEach((streamId) => { - this.groupKeyHistories[streamId] = new GroupKeyHistory(publisherGroupKeys[streamId]) - }) - } - - hasKey(streamId) { - return this.groupKeyHistories[streamId] !== undefined - } - - getLatestKey(streamId) { - if (this.groupKeyHistories[streamId]) { - return this.groupKeyHistories[streamId].getLatestKey() - } - return undefined - } - - getKeysBetween(streamId, start, end) { - if (this.groupKeyHistories[streamId]) { - return this.groupKeyHistories[streamId].getKeysBetween(start, end) - } - return [] - } - - addKey(streamId, groupKey, start) { - if (!this.groupKeyHistories[streamId]) { - this.groupKeyHistories[streamId] = new GroupKeyHistory() - } - this.groupKeyHistories[streamId].addKey(groupKey, start) - } -} diff --git a/src/KeyStorageUtil.js b/src/KeyStorageUtil.js deleted file mode 100644 index c289770aa..000000000 --- a/src/KeyStorageUtil.js +++ /dev/null @@ -1,42 +0,0 @@ -import KeyHistoryStorageUtil from './KeyHistoryStorageUtil' -import LatestKeyStorageUtil from './LatestKeyStorageUtil' -import EncryptionUtil from './EncryptionUtil' - -export default class KeyStorageUtil { - static getKeyStorageUtil(publisherGroupKeys = {}, storeHistoricalKeys = true) { - if (storeHistoricalKeys) { - return new KeyHistoryStorageUtil(publisherGroupKeys) - } - return new LatestKeyStorageUtil(publisherGroupKeys) - } - - static validateAndAddStart(publisherGroupKeys, subscriberGroupKeys) { - const validatedPublisherGroupKeys = {} - Object.keys(publisherGroupKeys).forEach((streamId) => { - validatedPublisherGroupKeys[streamId] = this._getValidatedKeyObject(publisherGroupKeys[streamId]) - }) - - const validatedSubscriberGroupKeys = {} - Object.keys(subscriberGroupKeys).forEach((streamId) => { - const streamGroupKeys = subscriberGroupKeys[streamId] - validatedSubscriberGroupKeys[streamId] = {} - Object.keys(streamGroupKeys).forEach((publisherId) => { - validatedSubscriberGroupKeys[streamId][publisherId] = this._getValidatedKeyObject(streamGroupKeys[publisherId]) - }) - }) - - return [validatedPublisherGroupKeys, validatedSubscriberGroupKeys] - } - - static _getValidatedKeyObject(groupKeyObjOrString) { - if (groupKeyObjOrString.groupKey && groupKeyObjOrString.start) { - EncryptionUtil.validateGroupKey(groupKeyObjOrString.groupKey) - return groupKeyObjOrString - } - EncryptionUtil.validateGroupKey(groupKeyObjOrString) - return { - groupKey: groupKeyObjOrString, - start: Date.now() - } - } -} diff --git a/src/LatestKeyStorageUtil.js b/src/LatestKeyStorageUtil.js deleted file mode 100644 index 21197d48e..000000000 --- a/src/LatestKeyStorageUtil.js +++ /dev/null @@ -1,30 +0,0 @@ -export default class LatestKeyStorageUtil { - constructor(publisherGroupKeys = {}) { - this.latestKeys = publisherGroupKeys - } - - hasKey(streamId) { - return this.latestKeys[streamId] !== undefined - } - - getLatestKey(streamId) { - return this.latestKeys[streamId] - } - - /* eslint-disable class-methods-use-this */ - getKeysBetween(streamId, start, end) { - throw new Error(`Cannot retrieve historical keys for stream ${streamId} between ${start} and ${end} because only the latest key is stored. - Set options.publisherStoreKeyHistory to true to store all historical keys.`) - } - /* eslint-enable class-methods-use-this */ - - addKey(streamId, groupKey, start) { - if (this.latestKeys[streamId] && this.latestKeys[streamId].start > start) { - throw new Error(`Cannot add an older key as latest key (${this.latestKeys[streamId].start} > ${start})`) - } - this.latestKeys[streamId] = { - groupKey, - start - } - } -} diff --git a/src/MessageCreationUtil.js b/src/MessageCreationUtil.js index 1c5b50c6e..17e7c2567 100644 --- a/src/MessageCreationUtil.js +++ b/src/MessageCreationUtil.js @@ -6,19 +6,12 @@ import { MessageLayer } from 'streamr-client-protocol' import { ethers } from 'ethers' import Stream from './rest/domain/Stream' -import EncryptionUtil from './EncryptionUtil' -import KeyStorageUtil from './KeyStorageUtil' -import KeyExchangeUtil from './KeyExchangeUtil' -import InvalidGroupKeyRequestError from './errors/InvalidGroupKeyRequestError' -import InvalidGroupKeyResponseError from './errors/InvalidGroupKeyResponseError' import InvalidMessageTypeError from './errors/InvalidMessageTypeError' -import { uuid } from './utils' const { StreamMessage, MessageID, MessageRef } = MessageLayer -const { getKeyExchangeStreamId } = KeyExchangeUtil export default class MessageCreationUtil { - constructor(auth, signer, getUserInfo, getStreamFunction, keyStorageUtil) { + constructor(auth, signer, getUserInfo, getStreamFunction) { this.auth = auth this._signer = signer this.getUserInfo = getUserInfo @@ -27,7 +20,6 @@ export default class MessageCreationUtil { max: 10000, }) this.publishedStreams = {} - this.keyStorageUtil = keyStorageUtil || KeyStorageUtil.getKeyStorageUtil() this.msgChainId = randomstring.generate(20) this.cachedHashes = {} } @@ -109,16 +101,12 @@ export default class MessageCreationUtil { return this.publishedStreams[key].prevSequenceNumber } - async createStreamMessage(streamObjectOrId, data, timestamp = Date.now(), partitionKey = null, groupKey) { + async createStreamMessage(streamObjectOrId, data, timestamp = Date.now(), partitionKey = null) { // Validate data if (typeof data !== 'object') { throw new Error(`Message data must be an object! Was: ${data}`) } - if (groupKey) { - EncryptionUtil.validateGroupKey(groupKey) - } - const stream = (streamObjectOrId instanceof Stream) ? streamObjectOrId : await this.getStream(streamObjectOrId) const streamPartition = this.computeStreamPartition(stream.partitions, partitionKey) const publisherId = await this.getPublisherId() @@ -131,101 +119,12 @@ export default class MessageCreationUtil { messageType: StreamMessage.MESSAGE_TYPES.MESSAGE, }) - if (groupKey && this.keyStorageUtil.hasKey(stream.id) && groupKey !== this.keyStorageUtil.getLatestKey(stream.id).groupKey) { - EncryptionUtil.encryptStreamMessageAndNewKey(groupKey, streamMessage, this.keyStorageUtil.getLatestKey(stream.id).groupKey) - this.keyStorageUtil.addKey(stream.id, groupKey) - } else if (groupKey || this.keyStorageUtil.hasKey(stream.id)) { - if (groupKey) { - this.keyStorageUtil.addKey(stream.id, groupKey) - } - EncryptionUtil.encryptStreamMessage(streamMessage, this.keyStorageUtil.getLatestKey(stream.id).groupKey) - } - if (this._signer) { await this._signer.signStreamMessage(streamMessage) } return streamMessage } - async createGroupKeyRequest({ - messagePublisherAddress, - streamId, - publicKey, - start, - end, - }) { - if (!this._signer) { - throw new Error('Cannot create unsigned group key request. Must authenticate with "privateKey" or "provider"') - } - const publisherId = await this.getPublisherId() - const requestId = uuid('GroupKeyRequest') - const data = { - streamId, - requestId, - publicKey, - } - if (start && end) { - data.range = { - start, - end, - } - } - const [messageId, prevMsgRef] = this.createDefaultMsgIdAndPrevRef(getKeyExchangeStreamId(messagePublisherAddress), publisherId) - const streamMessage = new StreamMessage({ - messageId, - prevMsgRef, - messageType: StreamMessage.MESSAGE_TYPES.GROUP_KEY_REQUEST, - content: data, - }) - await this._signer.signStreamMessage(streamMessage) - return streamMessage - } - - async createGroupKeyResponse({ subscriberAddress, streamId, requestId, encryptedGroupKeys }) { - if (!this._signer) { - throw new Error('Cannot create unsigned group key response. Must authenticate with "privateKey" or "provider"') - } - const publisherId = await this.getPublisherId() - const data = { - requestId, - streamId, - keys: encryptedGroupKeys, - } - const [messageId, prevMsgRef] = this.createDefaultMsgIdAndPrevRef(getKeyExchangeStreamId(subscriberAddress), publisherId) - const streamMessage = new StreamMessage({ - messageId, - prevMsgRef, - messageType: StreamMessage.MESSAGE_TYPES.GROUP_KEY_RESPONSE, - encryptionType: StreamMessage.ENCRYPTION_TYPES.RSA, - content: data, - }) - await this._signer.signStreamMessage(streamMessage) - return streamMessage - } - - async createErrorMessage({ keyExchangeStreamId, streamId, error, requestId }) { - if (!this._signer) { - throw new Error('Cannot create unsigned error message. Must authenticate with "privateKey" or "provider"') - } - const publisherId = await this.getPublisherId() - const data = { - code: MessageCreationUtil.getErrorCodeFromError(error), - message: error.message, - streamId, - requestId, - } - const [messageId, prevMsgRef] = this.createDefaultMsgIdAndPrevRef(keyExchangeStreamId, publisherId) - const streamMessage = new StreamMessage({ - messageId, - prevMsgRef, - messageType: StreamMessage.MESSAGE_TYPES.GROUP_KEY_ERROR_RESPONSE, - content: data, - }) - - await this._signer.signStreamMessage(streamMessage) - return streamMessage - } - createMsgIdAndPrevRef(streamId, streamPartition, timestamp, publisherId) { const key = streamId + streamPartition if (!this.publishedStreams[key]) { @@ -248,14 +147,6 @@ export default class MessageCreationUtil { } static getErrorCodeFromError(error) { - if (error instanceof InvalidGroupKeyRequestError) { - return 'INVALID_GROUP_KEY_REQUEST' - } - - if (error instanceof InvalidGroupKeyResponseError) { - return 'INVALID_GROUP_KEY_RESPONSE' - } - if (error instanceof InvalidMessageTypeError) { return 'INVALID_MESSAGE_TYPE' } diff --git a/src/RealTimeSubscription.js b/src/RealTimeSubscription.js index 384970f55..d2db47514 100644 --- a/src/RealTimeSubscription.js +++ b/src/RealTimeSubscription.js @@ -3,16 +3,12 @@ import uniqueId from 'lodash.uniqueid' import Subscription from './Subscription' import AbstractSubscription from './AbstractSubscription' -import EncryptionUtil from './EncryptionUtil' -import UnableToDecryptError from './errors/UnableToDecryptError' export default class RealTimeSubscription extends AbstractSubscription { constructor({ streamId, streamPartition, callback, - groupKeys, - onUnableToDecrypt = AbstractSubscription.defaultUnableToDecrypt, propagationTimeout, resendTimeout, orderMessages = true, @@ -22,8 +18,6 @@ export default class RealTimeSubscription extends AbstractSubscription { streamId, streamPartition, callback, - groupKeys, - onUnableToDecrypt, propagationTimeout, resendTimeout, orderMessages, @@ -37,7 +31,6 @@ export default class RealTimeSubscription extends AbstractSubscription { this.debug = debugFactory(`StreamrClient::${id}`) } - this.alreadyFailedToDecrypt = {} this.resending = false } @@ -53,26 +46,6 @@ export default class RealTimeSubscription extends AbstractSubscription { this.setResending(false) } - // passing publisherId separately to ensure it is lowercase (See call of this function in AbstractSubscription.js) - _decryptOrRequestGroupKey(msg, publisherId) { - let newGroupKey - try { - newGroupKey = EncryptionUtil.decryptStreamMessage(msg, this.groupKeys[publisherId]) - } catch (e) { - if (e instanceof UnableToDecryptError && !this.alreadyFailedToDecrypt[publisherId]) { - this._requestGroupKeyAndQueueMessage(msg) - this.alreadyFailedToDecrypt[publisherId] = true - return false - } - throw e - } - delete this.alreadyFailedToDecrypt[publisherId] - if (newGroupKey) { - this.groupKeys[publisherId] = newGroupKey - } - return true - } - /* eslint-disable class-methods-use-this */ hasResendOptions() { return false @@ -92,16 +65,6 @@ export default class RealTimeSubscription extends AbstractSubscription { this.resending = resending } - setGroupKeys(publisherId, groupKeys) { - if (groupKeys.length !== 1) { - throw new Error('Received multiple group keys for a real time subscription (expected one).') - } - /* eslint-disable prefer-destructuring */ - this.groupKeys[publisherId.toLowerCase()] = groupKeys[0] - /* eslint-enable prefer-destructuring */ - this._handleEncryptedQueuedMsgs(publisherId) - } - onDisconnected() { this.setState(Subscription.State.unsubscribed) } diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 7a28806bd..baa9e6971 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -18,11 +18,7 @@ import { waitFor, getVersionString } from './utils' import RealTimeSubscription from './RealTimeSubscription' import CombinedSubscription from './CombinedSubscription' import Subscription from './Subscription' -import EncryptionUtil from './EncryptionUtil' -import KeyExchangeUtil from './KeyExchangeUtil' -import KeyStorageUtil from './KeyStorageUtil' import ResendUtil from './ResendUtil' -import InvalidMessageTypeError from './errors/InvalidMessageTypeError' const { SubscribeRequest, @@ -57,11 +53,6 @@ export default class StreamrClient extends EventEmitter { retryResendAfter: 5000, gapFillTimeout: 5000, maxPublishQueueSize: 10000, - // encryption options - publisherStoreKeyHistory: true, - publisherGroupKeys: {}, // {streamId: groupKey} - subscriberGroupKeys: {}, // {streamId: {publisherId: groupKey}} - keyExchange: {}, streamrNodeAddress: '0xf3E5A65851C3779f468c9EcB32E6f25D9D68601a', streamrOperatorAddress: '0xc0aa4dC0763550161a6B59fa430361b5a26df28C', tokenAddress: '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', @@ -103,22 +94,6 @@ export default class StreamrClient extends EventEmitter { this.options.auth.privateKey = `0x${this.options.auth.privateKey}` } - if (this.options.keyExchange) { - this.encryptionUtil = new EncryptionUtil(this.options.keyExchange) - this.keyExchangeUtil = new KeyExchangeUtil(this) - } - - // add the start time to every group key if missing - const validated = KeyStorageUtil.validateAndAddStart(this.options.publisherGroupKeys, this.options.subscriberGroupKeys) - /* eslint-disable prefer-destructuring */ - this.options.publisherGroupKeys = validated[0] - this.options.subscriberGroupKeys = validated[1] - /* eslint-enable prefer-destructuring */ - - this.keyStorageUtil = KeyStorageUtil.getKeyStorageUtil( - this.options.publisherGroupKeys, this.options.publisherStoreKeyHistory - ) - this.publishQueue = [] this.session = new Session(this, this.options.auth) this.signer = Signer.createSigner({ @@ -246,7 +221,6 @@ export default class StreamrClient extends EventEmitter { this.debug('Connected!') this.emit('connected') try { - await this._subscribeToKeyExchangeStream() if (!this.isConnected()) { return } // Check pending subscriptions Object.keys(this.subscribedStreamPartitions).forEach((key) => { @@ -312,44 +286,6 @@ export default class StreamrClient extends EventEmitter { console.error(error) } - async _subscribeToKeyExchangeStream() { - if (!this.options.auth.privateKey && !this.options.auth.provider) { - return - } - await this.session.getSessionToken() // trigger auth errors if any - // subscribing to own keyexchange stream - const publisherId = await this.getPublisherId() - const streamId = KeyExchangeUtil.getKeyExchangeStreamId(publisherId) - this.subscribe(streamId, async (parsedContent, streamMessage) => { - if (streamMessage.messageType === StreamMessage.MESSAGE_TYPES.GROUP_KEY_REQUEST) { - if (this.keyExchangeUtil) { - try { - await this.keyExchangeUtil.handleGroupKeyRequest(streamMessage) - } catch (error) { - this.debug('WARN: %s', error.message) - const msg = streamMessage.getParsedContent() - const errorMessage = await this.msgCreationUtil.createErrorMessage({ - keyExchangeStreamId: streamId, - requestId: msg.requestId, - streamId: msg.streamId, - error, - }) - this.publishStreamMessage(errorMessage) - } - } - } else if (streamMessage.messageType === StreamMessage.MESSAGE_TYPES.GROUP_KEY_RESPONSE) { - if (this.keyExchangeUtil) { - this.keyExchangeUtil.handleGroupKeyResponse(streamMessage) - } - } else if (streamMessage.messageType === StreamMessage.MESSAGE_TYPES.GROUP_KEY_ERROR_RESPONSE) { - this.debug('WARN: Received error of type %s from %s: %s', - streamMessage.getParsedContent().code, streamMessage.getPublisherId(), streamMessage.getParsedContent().message) - } else { - throw new InvalidMessageTypeError(`Cannot handle message with type: ${streamMessage.messageType}`) - } - }) - } - _getSubscribedStreamPartition(streamId, streamPartition) { const key = streamId + streamPartition return this.subscribedStreamPartitions[key] @@ -406,7 +342,7 @@ export default class StreamrClient extends EventEmitter { return subs } - async publish(streamObjectOrId, data, timestamp = new Date(), partitionKey = null, groupKey) { + async publish(streamObjectOrId, data, timestamp = new Date(), partitionKey = null) { if (this.session.isUnauthenticated()) { throw new Error('Need to be authenticated to publish.') } @@ -423,7 +359,7 @@ export default class StreamrClient extends EventEmitter { const timestampAsNumber = timestamp instanceof Date ? timestamp.getTime() : new Date(timestamp).getTime() const [sessionToken, streamMessage] = await Promise.all([ this.session.getSessionToken(), - this.msgCreationUtil.createStreamMessage(streamObjectOrId, data, timestampAsNumber, partitionKey, groupKey), + this.msgCreationUtil.createStreamMessage(streamObjectOrId, data, timestampAsNumber, partitionKey), ]) if (this.isConnected()) { @@ -482,8 +418,6 @@ export default class StreamrClient extends EventEmitter { streamPartition: options.partition || 0, callback, options: options.resend, - groupKeys: this.options.subscriberGroupKeys[options.stream], - onUnableToDecrypt: options.onUnableToDecrypt, propagationTimeout: this.options.gapFillTimeout, resendTimeout: this.options.retryResendAfter, orderMessages: this.orderMessages, @@ -534,27 +468,6 @@ export default class StreamrClient extends EventEmitter { throw new Error('subscribe: Invalid arguments: options.stream is not given') } - if (options.groupKeys) { - const now = Date.now() - Object.keys(options.groupKeys).forEach((publisherId) => { - EncryptionUtil.validateGroupKey(options.groupKeys[publisherId]) - if (!this.options.subscriberGroupKeys[options.stream]) { - this.options.subscriberGroupKeys[options.stream] = {} - } - this.options.subscriberGroupKeys[options.stream][publisherId] = { - groupKey: options.groupKeys[publisherId], - start: now - } - }) - } - - const groupKeys = {} - if (this.options.subscriberGroupKeys[options.stream]) { - Object.keys(this.options.subscriberGroupKeys[options.stream]).forEach((publisherId) => { - groupKeys[publisherId] = this.options.subscriberGroupKeys[options.stream][publisherId].groupKey - }) - } - // Create the Subscription object and bind handlers let sub if (options.resend) { @@ -563,8 +476,6 @@ export default class StreamrClient extends EventEmitter { streamPartition: options.partition || 0, callback, options: options.resend, - groupKeys, - onUnableToDecrypt: options.onUnableToDecrypt, propagationTimeout: this.options.gapFillTimeout, resendTimeout: this.options.retryResendAfter, orderMessages: this.options.orderMessages, @@ -576,8 +487,6 @@ export default class StreamrClient extends EventEmitter { streamPartition: options.partition || 0, callback, options: options.resend, - groupKeys, - onUnableToDecrypt: options.onUnableToDecrypt, propagationTimeout: this.options.gapFillTimeout, resendTimeout: this.options.retryResendAfter, orderMessages: this.options.orderMessages, @@ -595,19 +504,6 @@ export default class StreamrClient extends EventEmitter { this.debug('done event for sub %d', sub.id) this.unsubscribe(sub) }) - sub.on('groupKeyMissing', async (messagePublisherAddress, start, end) => { - if (this.encryptionUtil) { - await this.encryptionUtil.onReady() - const streamMessage = await this.msgCreationUtil.createGroupKeyRequest({ - messagePublisherAddress, - streamId: sub.streamId, - publicKey: this.encryptionUtil.getPublicKey(), - start, - end, - }) - await this.publishStreamMessage(streamMessage) - } - }) // Add to lookups this._addSubscription(sub) @@ -914,20 +810,6 @@ export default class StreamrClient extends EventEmitter { return this.connection.send(request) } - // each element of the array "groupKeys" is an object with 2 fields: "groupKey" and "start" - _setGroupKeys(streamId, publisherId, groupKeys) { - if (!this.options.subscriberGroupKeys[streamId]) { - this.options.subscriberGroupKeys[streamId] = {} - } - const last = groupKeys[groupKeys.length - 1] - const current = this.options.subscriberGroupKeys[streamId][publisherId] - if (!current || last.start > current.start) { - this.options.subscriberGroupKeys[streamId][publisherId] = last - } - // TODO: fix this hack in other PR - this.subscribedStreamPartitions[streamId + '0'].setSubscriptionsGroupKeys(publisherId, groupKeys.map((obj) => obj.groupKey)) - } - handleError(msg) { this.debug(msg) this.emit('error', msg) diff --git a/src/SubscribedStreamPartition.js b/src/SubscribedStreamPartition.js index 3262b8259..81582aa7f 100644 --- a/src/SubscribedStreamPartition.js +++ b/src/SubscribedStreamPartition.js @@ -108,12 +108,6 @@ export default class SubscribedStreamPartition { delete this.subscriptions[sub.id] } } - - setSubscriptionsGroupKeys(publisherId, groupKeys) { - Object.values(this.subscriptions).forEach((sub) => { - sub.setGroupKeys(publisherId, groupKeys) - }) - } } SubscribedStreamPartition.memoizeOpts = memoizeOpts diff --git a/src/Subscription.js b/src/Subscription.js index 17375a861..11170eeaf 100644 --- a/src/Subscription.js +++ b/src/Subscription.js @@ -13,7 +13,6 @@ export default class Subscription extends EventEmitter { streamId, streamPartition, callback, - groupKeys, propagationTimeout = DEFAULT_PROPAGATION_TIMEOUT, resendTimeout = DEFAULT_RESEND_TIMEOUT, debug @@ -37,12 +36,6 @@ export default class Subscription extends EventEmitter { if (!streamId) { throw new Error('No stream id given!') } - this.groupKeys = {} - if (groupKeys) { - Object.keys(groupKeys).forEach((publisherId) => { - this.groupKeys[publisherId.toLowerCase()] = groupKeys[publisherId] - }) - } this.propagationTimeout = propagationTimeout this.resendTimeout = resendTimeout this.state = Subscription.State.unsubscribed diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index 14044f8ff..549fb590b 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -1,5 +1,4 @@ import assert from 'assert' -import crypto from 'crypto' import fs from 'fs' import path from 'path' @@ -103,19 +102,20 @@ describe('StreamrClient Connection', () => { }, 100) }) - it('emits error with connection', async (done) => { + it('does not emit error with connect', async (done) => { + // error will come through when getting session const client = createClient({ restUrl: 'asdasd', autoConnect: false, autoDisconnect: false, }) client.onError = jest.fn() - client.once('error', (error) => { - expect(error).toBeTruthy() - expect(client.onError).toHaveBeenCalledTimes(1) - done() - }) + client.once('error', done) client.connect() + setTimeout(() => { + expect(client.onError).not.toHaveBeenCalled() + done() + }, 100) }) }) @@ -979,117 +979,4 @@ describe('StreamrClient', () => { }) }, 10000) }) - - describe.skip('decryption', () => { - it('client.subscribe can decrypt encrypted messages if it knows the group key', async (done) => { - client.once('error', done) - const id = Date.now() - const publisherId = await client.getPublisherId() - const groupKey = crypto.randomBytes(32) - const keys = { - [publisherId]: groupKey, - } - - const sub = client.subscribe({ - stream: stream.id, - groupKeys: keys, - }, (parsedContent, streamMessage) => { - assert.equal(parsedContent.id, id) - - // Check signature stuff - assert.strictEqual(streamMessage.signatureType, StreamMessage.SIGNATURE_TYPES.ETH) - assert(streamMessage.getPublisherId()) - assert(streamMessage.signature) - - // All good, unsubscribe - client.unsubscribe(sub) - sub.once('unsubscribed', () => { - done() - }) - }) - - // Publish after subscribed - sub.once('subscribed', () => { - client.publish(stream.id, { - id, - }, Date.now(), null, groupKey) - }) - }) - - it('client.subscribe can get the group key and decrypt encrypted messages using an RSA key pair', async (done) => { - client.once('error', done) - const id = Date.now() - const groupKey = crypto.randomBytes(32) - // subscribe without knowing the group key to decrypt stream messages - const sub = client.subscribe({ - stream: stream.id, - }, (parsedContent, streamMessage) => { - assert.equal(parsedContent.id, id) - - // Check signature stuff - assert.strictEqual(streamMessage.signatureType, StreamMessage.SIGNATURE_TYPES.ETH) - assert(streamMessage.getPublisherId()) - assert(streamMessage.signature) - - // Now the subscriber knows the group key - assert.deepStrictEqual(sub.groupKeys[streamMessage.getPublisherId().toLowerCase()], groupKey) - - sub.once('unsubscribed', () => { - done() - }) - - // All good, unsubscribe - client.unsubscribe(sub) - }) - - // Publish after subscribed - sub.once('subscribed', () => { - client.publish(stream.id, { - id, - }, Date.now(), null, groupKey) - }) - }, 2 * TIMEOUT) - - it('client.subscribe with resend last can get the historical keys for previous encrypted messages', (done) => { - client.once('error', done) - // Publish encrypted messages with different keys - const groupKey1 = crypto.randomBytes(32) - const groupKey2 = crypto.randomBytes(32) - client.publish(stream.id, { - test: 'resent msg 1', - }, Date.now(), null, groupKey1) - client.publish(stream.id, { - test: 'resent msg 2', - }, Date.now(), null, groupKey2) - - // Add delay: this test needs some time to allow the message to be written to Cassandra - let receivedFirst = false - setTimeout(() => { - // subscribe with resend without knowing the historical keys - const sub = client.subscribe({ - stream: stream.id, - resend: { - last: 2, - }, - }, async (parsedContent) => { - // Check message content - if (!receivedFirst) { - assert.strictEqual(parsedContent.test, 'resent msg 1') - receivedFirst = true - } else { - assert.strictEqual(parsedContent.test, 'resent msg 2') - } - - sub.once('unsubscribed', () => { - // TODO: fix this hack in other PR - assert.strictEqual(client.subscribedStreamPartitions[stream.id + '0'], undefined) - done() - }) - - // All good, unsubscribe - client.unsubscribe(sub) - }) - }, TIMEOUT * 0.8) - }, 2 * TIMEOUT) - }) }) diff --git a/test/unit/EncryptionUtil.test.js b/test/unit/EncryptionUtil.test.js deleted file mode 100644 index 743252893..000000000 --- a/test/unit/EncryptionUtil.test.js +++ /dev/null @@ -1,226 +0,0 @@ -import crypto from 'crypto' - -import { ethers } from 'ethers' -import { MessageLayer } from 'streamr-client-protocol' - -import EncryptionUtil from '../../src/EncryptionUtil' - -const { StreamMessage, MessageID } = MessageLayer - -// wrap these tests so can run same tests as if in browser -function TestEncryptionUtil({ isBrowser = false } = {}) { - describe(`EncryptionUtil ${isBrowser ? 'Browser' : 'Server'}`, () => { - beforeAll(() => { - // this is the toggle used in EncryptionUtil to - // use the webcrypto apis - process.browser = !!isBrowser - }) - afterAll(() => { - process.browser = !isBrowser - }) - - it('rsa decryption after encryption equals the initial plaintext', async () => { - const encryptionUtil = new EncryptionUtil() - await encryptionUtil.onReady() - const plaintext = 'some random text' - const ciphertext = EncryptionUtil.encryptWithPublicKey(Buffer.from(plaintext, 'utf8'), encryptionUtil.getPublicKey()) - expect(encryptionUtil.decryptWithPrivateKey(ciphertext).toString('utf8')).toStrictEqual(plaintext) - }) - - it('rsa decryption after encryption equals the initial plaintext (hex strings)', async () => { - const encryptionUtil = new EncryptionUtil() - await encryptionUtil.onReady() - const plaintext = 'some random text' - const ciphertext = EncryptionUtil.encryptWithPublicKey(Buffer.from(plaintext, 'utf8'), encryptionUtil.getPublicKey(), true) - expect(encryptionUtil.decryptWithPrivateKey(ciphertext, true).toString('utf8')).toStrictEqual(plaintext) - }) - - it('aes decryption after encryption equals the initial plaintext', () => { - const key = crypto.randomBytes(32) - const plaintext = 'some random text' - const ciphertext = EncryptionUtil.encrypt(Buffer.from(plaintext, 'utf8'), key) - expect(EncryptionUtil.decrypt(ciphertext, key).toString('utf8')).toStrictEqual(plaintext) - }) - - it('aes encryption preserves size (plus iv)', () => { - const key = crypto.randomBytes(32) - const plaintext = 'some random text' - const plaintextBuffer = Buffer.from(plaintext, 'utf8') - const ciphertext = EncryptionUtil.encrypt(plaintextBuffer, key) - const ciphertextBuffer = ethers.utils.arrayify(`0x${ciphertext}`) - expect(ciphertextBuffer.length).toStrictEqual(plaintextBuffer.length + 16) - }) - - it('multiple same encrypt() calls use different ivs and produce different ciphertexts', () => { - const key = crypto.randomBytes(32) - const plaintext = 'some random text' - const ciphertext1 = EncryptionUtil.encrypt(Buffer.from(plaintext, 'utf8'), key) - const ciphertext2 = EncryptionUtil.encrypt(Buffer.from(plaintext, 'utf8'), key) - expect(ciphertext1.slice(0, 32)).not.toStrictEqual(ciphertext2.slice(0, 32)) - expect(ciphertext1.slice(32)).not.toStrictEqual(ciphertext2.slice(32)) - }) - - it('StreamMessage gets encrypted', () => { - const key = crypto.randomBytes(32) - const streamMessage = new StreamMessage({ - messageId: new MessageID('streamId', 0, 1, 0, 'publisherId', 'msgChainId'), - prevMesssageRef: null, - content: { - foo: 'bar', - }, - messageType: StreamMessage.MESSAGE_TYPES.MESSAGE, - encryptionType: StreamMessage.ENCRYPTION_TYPES.NONE, - signatureType: StreamMessage.SIGNATURE_TYPES.NONE, - signature: null, - }) - EncryptionUtil.encryptStreamMessage(streamMessage, key) - expect(streamMessage.getSerializedContent()).not.toStrictEqual('{"foo":"bar"}') - expect(streamMessage.encryptionType).toStrictEqual(StreamMessage.ENCRYPTION_TYPES.AES) - }) - - it('StreamMessage decryption after encryption equals the initial StreamMessage', () => { - const key = crypto.randomBytes(32) - const streamMessage = new StreamMessage({ - messageId: new MessageID('streamId', 0, 1, 0, 'publisherId', 'msgChainId'), - prevMesssageRef: null, - content: { - foo: 'bar', - }, - messageType: StreamMessage.MESSAGE_TYPES.MESSAGE, - encryptionType: StreamMessage.ENCRYPTION_TYPES.NONE, - signatureType: StreamMessage.SIGNATURE_TYPES.NONE, - signature: null, - }) - EncryptionUtil.encryptStreamMessage(streamMessage, key) - const newKey = EncryptionUtil.decryptStreamMessage(streamMessage, key) - expect(newKey).toBe(null) - expect(streamMessage.getSerializedContent()).toStrictEqual('{"foo":"bar"}') - expect(streamMessage.encryptionType).toStrictEqual(StreamMessage.ENCRYPTION_TYPES.NONE) - }) - - it('StreamMessage gets encrypted with new key', () => { - const key = crypto.randomBytes(32) - const newKey = crypto.randomBytes(32) - const streamMessage = new StreamMessage({ - messageId: new MessageID('streamId', 0, 1, 0, 'publisherId', 'msgChainId'), - prevMesssageRef: null, - content: { - foo: 'bar', - }, - messageType: StreamMessage.MESSAGE_TYPES.MESSAGE, - encryptionType: StreamMessage.ENCRYPTION_TYPES.NONE, - signatureType: StreamMessage.SIGNATURE_TYPES.NONE, - signature: null, - }) - EncryptionUtil.encryptStreamMessageAndNewKey(newKey, streamMessage, key) - expect(streamMessage.getSerializedContent()).not.toStrictEqual('{"foo":"bar"}') - expect(streamMessage.encryptionType).toStrictEqual(StreamMessage.ENCRYPTION_TYPES.NEW_KEY_AND_AES) - }) - - it('StreamMessage decryption after encryption equals the initial StreamMessage (with new key)', () => { - const key = crypto.randomBytes(32) - const newKey = crypto.randomBytes(32) - const streamMessage = new StreamMessage({ - messageId: new MessageID('streamId', 0, 1, 0, 'publisherId', 'msgChainId'), - prevMesssageRef: null, - content: { - foo: 'bar', - }, - messageType: StreamMessage.MESSAGE_TYPES.MESSAGE, - encryptionType: StreamMessage.ENCRYPTION_TYPES.NONE, - signatureType: StreamMessage.SIGNATURE_TYPES.NONE, - signature: null, - }) - EncryptionUtil.encryptStreamMessageAndNewKey(newKey, streamMessage, key) - const newKeyReceived = EncryptionUtil.decryptStreamMessage(streamMessage, key) - expect(newKeyReceived).toStrictEqual(newKey) - expect(streamMessage.getSerializedContent()).toStrictEqual('{"foo":"bar"}') - expect(streamMessage.encryptionType).toStrictEqual(StreamMessage.ENCRYPTION_TYPES.NONE) - }) - - it('throws if invalid public key passed in the constructor', () => { - const keys = crypto.generateKeyPairSync('rsa', { - modulusLength: 4096, - publicKeyEncoding: { - type: 'spki', - format: 'pem', - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem', - }, - }) - expect(() => { - // eslint-disable-next-line no-new - new EncryptionUtil({ - privateKey: keys.privateKey, - publicKey: 'wrong public key', - }) - }).toThrow() - }) - - it('throws if invalid private key passed in the constructor', () => { - const keys = crypto.generateKeyPairSync('rsa', { - modulusLength: 4096, - publicKeyEncoding: { - type: 'spki', - format: 'pem', - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem', - }, - }) - expect(() => { - // eslint-disable-next-line no-new - new EncryptionUtil({ - privateKey: 'wrong private key', - publicKey: keys.publicKey, - }) - }).toThrow() - }) - - it('does not throw if valid key pair passed in the constructor', () => { - const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', { - modulusLength: 4096, - publicKeyEncoding: { - type: 'spki', - format: 'pem', - }, - privateKeyEncoding: { - type: 'pkcs8', - format: 'pem', - }, - }) - // eslint-disable-next-line no-new - new EncryptionUtil({ - privateKey, - publicKey, - }) - }) - - it('validateGroupKey() throws if key is the wrong size', () => { - expect(() => { - EncryptionUtil.validateGroupKey(crypto.randomBytes(16)) - }).toThrow() - }) - - it('validateGroupKey() throws if key is not a buffer', () => { - expect(() => { - EncryptionUtil.validateGroupKey(ethers.utils.hexlify(crypto.randomBytes(32))) - }).toThrow() - }) - - it('validateGroupKey() does not throw', () => { - EncryptionUtil.validateGroupKey(crypto.randomBytes(32)) - }) - }) -} - -TestEncryptionUtil({ - isBrowser: false, -}) - -TestEncryptionUtil({ - isBrowser: true, -}) diff --git a/test/unit/HistoricalSubscription.test.js b/test/unit/HistoricalSubscription.test.js index 162c72634..582dc880d 100644 --- a/test/unit/HistoricalSubscription.test.js +++ b/test/unit/HistoricalSubscription.test.js @@ -1,11 +1,8 @@ -import crypto from 'crypto' - import sinon from 'sinon' import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' import HistoricalSubscription from '../../src/HistoricalSubscription' -import EncryptionUtil from '../../src/EncryptionUtil' import Subscription from '../../src/Subscription' const { StreamMessage, MessageIDStrict, MessageRef } = MessageLayer @@ -553,209 +550,6 @@ describe('HistoricalSubscription', () => { sub.handleResentMessage(byeMsg, 'requestId', sinon.stub().resolves(true)) }) - - describe('decryption', () => { - let sub - - afterEach(() => { - sub.stop() - }) - - it('should read clear text content without trying to decrypt', (done) => { - const msg1 = createMsg(1, 0, null, 0, { - foo: 'bar', - }) - sub = new HistoricalSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: (content) => { - expect(content).toStrictEqual(msg1.getParsedContent()) - done() - }, - options: { - last: 1, - }, - }) - return sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) - }) - - it('should decrypt encrypted content with the correct key', (done) => { - const groupKey = crypto.randomBytes(32) - const data = { - foo: 'bar', - } - const msg1 = createMsg(1, 0, null, 0, data) - EncryptionUtil.encryptStreamMessage(msg1, groupKey) - sub = new HistoricalSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: (content) => { - expect(content).toStrictEqual(data) - done() - }, - options: { - last: 1, - }, - groupKeys: { - publisherId: groupKey, - } - }) - return sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) - }) - - it('should emit "groupKeyMissing" with range when no historical group keys are set', (done) => { - const correctGroupKey = crypto.randomBytes(32) - const msg1 = createMsg(1, 0, null, 0, { - foo: 'bar', - }) - EncryptionUtil.encryptStreamMessage(msg1, correctGroupKey) - sub = new HistoricalSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: () => {}, - options: { - last: 1, - }, - }) - sub.on('groupKeyMissing', (publisherId, start, end) => { - expect(publisherId).toBe(msg1.getPublisherId()) - expect(start).toBe(msg1.getTimestamp()) - expect(end).toBeTruthy() - done() - }) - return sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) - }) - - it('should queue messages when not able to decrypt and handle them once the keys are set', async () => { - const groupKey1 = crypto.randomBytes(32) - const groupKey2 = crypto.randomBytes(32) - const data1 = { - test: 'data1', - } - const data2 = { - test: 'data2', - } - const msg1 = createMsg(1, 0, null, 0, data1) - const msg2 = createMsg(2, 0, 1, 0, data2) - EncryptionUtil.encryptStreamMessage(msg1, groupKey1) - EncryptionUtil.encryptStreamMessage(msg2, groupKey2) - let received1 = null - let received2 = null - sub = new HistoricalSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: (content) => { - if (!received1) { - received1 = content - } else { - received2 = content - } - }, - options: { - last: 1, - }, - }) - // cannot decrypt msg1, queues it and emits "groupKeyMissing" (should send group key request). - await sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) - // cannot decrypt msg2, queues it. - await sub.handleResentMessage(msg2, 'requestId', sinon.stub().resolves(true)) - // faking the reception of the group key response - sub.setGroupKeys('publisherId', [groupKey1, groupKey2]) - // try again to decrypt the queued messages but this time with the correct key - expect(received1).toStrictEqual(data1) - expect(received2).toStrictEqual(data2) - }) - - it('should call "onUnableToDecrypt" when not able to decrypt with historical keys set', async () => { - const correctGroupKey = crypto.randomBytes(32) - const wrongGroupKey = crypto.randomBytes(32) - const data1 = { - test: 'data1', - } - const data2 = { - test: 'data2', - } - const msg1 = createMsg(1, 0, null, 0, data1) - const msg2 = createMsg(2, 0, 1, 0, data2) - EncryptionUtil.encryptStreamMessage(msg1, correctGroupKey) - EncryptionUtil.encryptStreamMessage(msg2, correctGroupKey) - let undecryptableMsg = null - sub = new HistoricalSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: () => { - throw new Error('should not call the handler') - }, - options: { - last: 1, - }, - onUnableToDecrypt: (error) => { - undecryptableMsg = error.streamMessage - } - }) - // cannot decrypt msg1, emits "groupKeyMissing" (should send group key request). - await sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) - // cannot decrypt msg2, queues it. - await sub.handleResentMessage(msg2, 'requestId', sinon.stub().resolves(true)) - // faking the reception of the group key response - sub.setGroupKeys('publisherId', [wrongGroupKey]) - - expect(undecryptableMsg).toStrictEqual(msg2) - }) - - it('should queue messages when not able to decrypt and handle them once the keys are set (multiple publishers)', async () => { - const groupKey1 = crypto.randomBytes(32) - const groupKey2 = crypto.randomBytes(32) - const groupKey3 = crypto.randomBytes(32) - const data1 = { - test: 'data1', - } - const data2 = { - test: 'data2', - } - const data3 = { - test: 'data3', - } - const data4 = { - test: 'data4', - } - const msg1 = createMsg(1, 0, null, 0, data1, 'publisherId1') - const msg2 = createMsg(2, 0, 1, 0, data2, 'publisherId1') - const msg3 = createMsg(1, 0, null, 0, data3, 'publisherId2') - const msg4 = createMsg(2, 0, 1, 0, data4, 'publisherId2') - EncryptionUtil.encryptStreamMessage(msg1, groupKey1) - EncryptionUtil.encryptStreamMessage(msg2, groupKey2) - EncryptionUtil.encryptStreamMessage(msg3, groupKey3) - EncryptionUtil.encryptStreamMessage(msg4, groupKey3) - const received = [] - sub = new HistoricalSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: (content) => { - received.push(content) - }, - options: { - last: 1, - }, - }) - // cannot decrypt msg1, queues it and emits "groupKeyMissing" (should send group key request). - await sub.handleResentMessage(msg1, 'requestId', sinon.stub().resolves(true)) - // cannot decrypt msg2, queues it. - await sub.handleResentMessage(msg2, 'requestId', sinon.stub().resolves(true)) - // cannot decrypt msg3, queues it and emits "groupKeyMissing" (should send group key request). - await sub.handleResentMessage(msg3, 'requestId', sinon.stub().resolves(true)) - // cannot decrypt msg4, queues it. - await sub.handleResentMessage(msg4, 'requestId', sinon.stub().resolves(true)) - // faking the reception of the group key response - sub.setGroupKeys('publisherId2', [groupKey3]) - sub.setGroupKeys('publisherId1', [groupKey1, groupKey2]) - // try again to decrypt the queued messages but this time with the correct key - expect(received[0]).toStrictEqual(data3) - expect(received[1]).toStrictEqual(data4) - expect(received[2]).toStrictEqual(data1) - expect(received[3]).toStrictEqual(data2) - }) - }) }) describe('handleError()', () => { diff --git a/test/unit/KeyExchangeUtil.test.js b/test/unit/KeyExchangeUtil.test.js deleted file mode 100644 index d61556660..000000000 --- a/test/unit/KeyExchangeUtil.test.js +++ /dev/null @@ -1,325 +0,0 @@ -import crypto from 'crypto' - -import sinon from 'sinon' -import { MessageLayer } from 'streamr-client-protocol' -import debugFactory from 'debug' - -import KeyExchangeUtil from '../../src/KeyExchangeUtil' -import EncryptionUtil from '../../src/EncryptionUtil' -import KeyStorageUtil from '../../src/KeyStorageUtil' -import InvalidGroupKeyResponseError from '../../src/errors/InvalidGroupKeyResponseError' -import InvalidGroupKeyRequestError from '../../src/errors/InvalidGroupKeyRequestError' -import { uid } from '../utils' - -const { StreamMessage, MessageIDStrict } = MessageLayer -const subscribers = ['0xb8CE9ab6943e0eCED004cDe8e3bBed6568B2Fa01'.toLowerCase(), 'subscriber2', 'subscriber3'] -const subscribersMap = {} -subscribers.forEach((p) => { - subscribersMap[p] = true -}) - -async function setupClient() { - const client = {} - client.debug = debugFactory('StreamrClient::test') - client.getStreamSubscribers = sinon.stub() - client.getStreamSubscribers.withArgs('streamId').resolves(subscribers) - client.isStreamSubscriber = sinon.stub() - client.isStreamSubscriber.withArgs('streamId', 'subscriber4').resolves(true) - client.isStreamSubscriber.withArgs('streamId', 'subscriber5').resolves(false) - client.keyStorageUtil = KeyStorageUtil.getKeyStorageUtil() - client.keyStorageUtil.addKey('streamId', crypto.randomBytes(32), 5) - client.keyStorageUtil.addKey('streamId', crypto.randomBytes(32), 12) - client.keyStorageUtil.addKey('streamId', crypto.randomBytes(32), 17) - client.keyStorageUtil.addKey('streamId', crypto.randomBytes(32), 25) - client.keyStorageUtil.addKey('streamId', crypto.randomBytes(32), 35) - client.subscribedStreamPartitions = { - streamId0: { // 'streamId' + 0 (stream partition) - setSubscriptionsGroupKey: sinon.stub(), - }, - } - client.encryptionUtil = new EncryptionUtil() - await client.encryptionUtil.onReady() - return client -} - -describe.skip('KeyExchangeUtil', () => { - let client - let util - beforeEach(async () => { - client = await setupClient() - util = new KeyExchangeUtil(client) - }) - - describe('getSubscribers', () => { - it('should use endpoint to retrieve subscribers', async () => { - const retrievedSubscribers = await util.getSubscribers('streamId') - expect(client.getStreamSubscribers.calledOnce).toBeTruthy() - expect(subscribersMap).toStrictEqual(retrievedSubscribers) - expect(await util.subscribersPromise).toStrictEqual(subscribersMap) - }) - - it('should use stored subscribers and not the endpoint', async () => { - util.subscribersPromise = Promise.resolve(subscribersMap) - const retrievedSubscribers = await util.getSubscribers('streamId') - expect(client.getStreamSubscribers.notCalled).toBeTruthy() - expect(subscribersMap).toStrictEqual(retrievedSubscribers) - }) - - it('should call getStreamPublishers only once when multiple calls made simultaneously', async () => { - const p1 = util.getSubscribers('streamId') - const p2 = util.getSubscribers('streamId') - const [subscribers1, subscribers2] = await Promise.all([p1, p2]) - expect(client.getStreamSubscribers.calledOnce).toBeTruthy() - expect(subscribers1).toStrictEqual(subscribers2) - }) - - it('should use endpoint again after the list of locally stored publishers expires', async () => { - const clock = sinon.useFakeTimers() - await util.getSubscribers('streamId') - util.subscribersPromise = Promise.resolve(subscribersMap) - await util.getSubscribers('streamId') - clock.tick(KeyExchangeUtil.SUBSCRIBERS_EXPIRATION_TIME + 100) - await util.getSubscribers('streamId') - expect(client.getStreamSubscribers.calledTwice).toBeTruthy() - clock.restore() - }) - }) - - describe('handleGroupKeyRequest', () => { - it('should reject request for a stream for which the client does not have a group key', async (done) => { - const requestId = uid('requestId') - const streamMessage = new StreamMessage({ - messageId: new MessageIDStrict('clientKeyExchangeAddress', 0, Date.now(), 0, 'subscriber2', ''), - prevMsgRef: null, - content: { - streamId: 'wrong-streamId', - publicKey: 'rsa-public-key', - requestId, - }, - messageType: StreamMessage.MESSAGE_TYPES.GROUP_KEY_REQUEST, - encryptionType: StreamMessage.ENCRYPTION_TYPES.NONE, - signatureType: StreamMessage.SIGNATURE_TYPES.ETH, - signature: 'signature', - }) - - await util.handleGroupKeyRequest(streamMessage).catch((err) => { - expect(err).toBeInstanceOf(InvalidGroupKeyRequestError) - expect(err.message).toBe('Received group key request for stream \'wrong-streamId\' but no group key is set') - done() - }) - }) - - it('should send group key response (latest key)', async (done) => { - const requestId = uid('requestId') - const subscriberKeyPair = new EncryptionUtil() - await subscriberKeyPair.onReady() - const streamMessage = new StreamMessage({ - messageId: new MessageIDStrict('clientKeyExchangeAddress', 0, Date.now(), 0, 'subscriber2', ''), - prevMsgRef: null, - content: { - streamId: 'streamId', - publicKey: subscriberKeyPair.getPublicKey(), - requestId, - }, - messageType: StreamMessage.MESSAGE_TYPES.GROUP_KEY_REQUEST, - encryptionType: StreamMessage.ENCRYPTION_TYPES.NONE, - signatureType: StreamMessage.SIGNATURE_TYPES.ETH, - signature: 'signature', - }) - - client.msgCreationUtil = { - createGroupKeyResponse: ({ subscriberAddress, streamId, encryptedGroupKeys }) => { - expect(subscriberAddress).toBe('subscriber2') - expect(streamId).toBe('streamId') - expect(encryptedGroupKeys.length).toBe(1) - const keyObject = encryptedGroupKeys[0] - const expectedKeyObj = client.keyStorageUtil.getLatestKey('streamId') - expect(subscriberKeyPair.decryptWithPrivateKey(keyObject.groupKey, true)).toStrictEqual(expectedKeyObj.groupKey) - expect(keyObject.start).toStrictEqual(expectedKeyObj.start) - return Promise.resolve('fake response') - }, - } - client.publishStreamMessage = (response) => { - expect(response).toBe('fake response') - done() - } - - await util.handleGroupKeyRequest(streamMessage) - }) - - it('should send group key response (range of keys)', async (done) => { - const requestId = uid('requestId') - const subscriberKeyPair = new EncryptionUtil() - await subscriberKeyPair.onReady() - const streamMessage = new StreamMessage({ - messageId: new MessageIDStrict('clientKeyExchangeAddress', 0, Date.now(), 0, 'subscriber2', ''), - prevMsgRef: null, - content: { - streamId: 'streamId', - publicKey: subscriberKeyPair.getPublicKey(), - requestId, - range: { - start: 15, - end: 27 - } - }, - messageType: StreamMessage.MESSAGE_TYPES.GROUP_KEY_REQUEST, - encryptionType: StreamMessage.ENCRYPTION_TYPES.NONE, - signatureType: StreamMessage.SIGNATURE_TYPES.ETH, - signature: 'signature', - }) - - client.msgCreationUtil = { - createGroupKeyResponse: ({ subscriberAddress, streamId, encryptedGroupKeys }) => { - expect(subscriberAddress).toBe('subscriber2') - expect(streamId).toBe('streamId') - const decryptedKeys = [] - encryptedGroupKeys.forEach((keyObj) => { - const decryptedKey = subscriberKeyPair.decryptWithPrivateKey(keyObj.groupKey, true) - decryptedKeys.push({ - groupKey: decryptedKey, - start: keyObj.start - }) - }) - expect(decryptedKeys).toStrictEqual(client.keyStorageUtil.getKeysBetween('streamId', 15, 27)) - return Promise.resolve('fake response') - }, - } - - client.publishStreamMessage = (response) => { - expect(response).toBe('fake response') - done() - } - - await util.handleGroupKeyRequest(streamMessage) - }) - - it('should send group key response (latest key and no storage of past keys)', async (done) => { - const requestId = uid('requestId') - const subscriberKeyPair = new EncryptionUtil() - await subscriberKeyPair.onReady() - const streamMessage = new StreamMessage({ - messageId: new MessageIDStrict('clientKeyExchangeAddress', 0, Date.now(), 0, 'subscriber2', ''), - prevMsgRef: null, - content: { - requestId, - streamId: 'streamId', - publicKey: subscriberKeyPair.getPublicKey(), - }, - messageType: StreamMessage.MESSAGE_TYPES.GROUP_KEY_REQUEST, - encryptionType: StreamMessage.ENCRYPTION_TYPES.NONE, - signatureType: StreamMessage.SIGNATURE_TYPES.ETH, - signature: 'signature', - }) - client.msgCreationUtil = { - createGroupKeyResponse: ({ subscriberAddress, streamId, encryptedGroupKeys }) => { - expect(subscriberAddress).toBe('subscriber2') - expect(streamId).toBe('streamId') - expect(encryptedGroupKeys.length).toBe(1) - const keyObject = encryptedGroupKeys[0] - const expectedKeyObj = client.keyStorageUtil.getLatestKey('streamId') - expect(subscriberKeyPair.decryptWithPrivateKey(keyObject.groupKey, true)).toStrictEqual(expectedKeyObj.groupKey) - expect(keyObject.start).toStrictEqual(expectedKeyObj.start) - return Promise.resolve('fake response') - }, - } - client.publishStreamMessage = (response) => { - expect(response).toBe('fake response') - done() - } - util.handleGroupKeyRequest(streamMessage) - }) - }) - - describe('handleGroupKeyResponse', () => { - it('should reject response for a stream to which the client is not subscribed', async (done) => { - const requestId = uid('requestId') - const streamMessage = new StreamMessage({ - messageId: new MessageIDStrict('clientKeyExchangeAddress', 0, Date.now(), 0, 'publisherId', ''), - prevMsgRef: null, - content: { - streamId: 'wrong-streamId', - requestId, - keys: [{ - groupKey: 'encrypted-group-key', - start: 54256, - }], - }, - messageType: StreamMessage.MESSAGE_TYPES.GROUP_KEY_RESPONSE, - encryptionType: StreamMessage.ENCRYPTION_TYPES.RSA, - signatureType: StreamMessage.SIGNATURE_TYPES.ETH, - signature: 'signature', - }) - - try { - util.handleGroupKeyResponse(streamMessage) - } catch (err) { - expect(err).toBeInstanceOf(InvalidGroupKeyResponseError) - expect(err.message).toBe('Received group key response for a stream to which the client is not subscribed.') - done() - } - }) - - it('should reject response with invalid group key', async (done) => { - const requestId = uid('requestId') - const encryptedGroupKey = EncryptionUtil.encryptWithPublicKey(crypto.randomBytes(16), client.encryptionUtil.getPublicKey(), true) - const streamMessage = new StreamMessage({ - messageId: new MessageIDStrict('clientKeyExchangeAddress', 0, Date.now(), 0, 'publisherId', ''), - prevMsgRef: null, - content: { - streamId: 'streamId', - requestId, - keys: [{ - groupKey: encryptedGroupKey, - start: 54256, - }], - }, - messageType: StreamMessage.MESSAGE_TYPES.GROUP_KEY_RESPONSE, - encryptionType: StreamMessage.ENCRYPTION_TYPES.RSA, - signatureType: StreamMessage.SIGNATURE_TYPES.ETH, - signature: 'signature', - }) - try { - util.handleGroupKeyResponse(streamMessage) - } catch (err) { - expect(err).toBeInstanceOf(InvalidGroupKeyResponseError) - expect(err.message).toBe('Group key must have a size of 256 bits, not 128') - done() - } - }) - - it('should update client options and subscriptions with received group key', async (done) => { - const requestId = uid('requestId') - const groupKey = crypto.randomBytes(32) - const encryptedGroupKey = EncryptionUtil.encryptWithPublicKey(groupKey, client.encryptionUtil.getPublicKey(), true) - const streamMessage = new StreamMessage({ - messageId: new MessageIDStrict('clientKeyExchangeAddress', 0, Date.now(), 0, 'publisherId', ''), - prevMsgRef: null, - content: { - streamId: 'streamId', - requestId, - keys: [{ - groupKey: encryptedGroupKey, - start: 54256, - }], - }, - messageType: StreamMessage.MESSAGE_TYPES.GROUP_KEY_RESPONSE, - encryptionType: StreamMessage.ENCRYPTION_TYPES.RSA, - signatureType: StreamMessage.SIGNATURE_TYPES.ETH, - signature: 'signature', - }) - - // eslint-disable-next-line no-underscore-dangle - client._setGroupKeys = (streamId, publisherId, keys) => { - expect(streamId).toBe('streamId') - expect(publisherId).toBe('publisherId') - expect(keys).toStrictEqual([{ - groupKey, - start: 54256 - }]) - done() - } - await util.handleGroupKeyResponse(streamMessage) - }) - }) -}) diff --git a/test/unit/KeyHistoryStorageUtil.test.js b/test/unit/KeyHistoryStorageUtil.test.js deleted file mode 100644 index a493117c2..000000000 --- a/test/unit/KeyHistoryStorageUtil.test.js +++ /dev/null @@ -1,122 +0,0 @@ -import crypto from 'crypto' - -import KeyStorageUtil from '../../src/KeyStorageUtil' - -describe.skip('KeyHistoryStorageUtil', () => { - describe('hasKey()', () => { - it('returns true iff there is a GroupKeyHistory for the stream', () => { - const util = KeyStorageUtil.getKeyStorageUtil({ - streamId: { - groupKey: crypto.randomBytes(32), - start: Date.now() - } - }) - - expect(util.hasKey('streamId')).toBe(true) - expect(util.hasKey('wrong-streamId')).toBe(false) - }) - }) - - describe('addKey()', () => { - it('throws if adding an older key', () => { - const util = KeyStorageUtil.getKeyStorageUtil({ - streamId: { - groupKey: crypto.randomBytes(32), - start: Date.now() - } - }) - - expect(() => { - util.addKey('streamId', crypto.randomBytes(32), 0) - }).toThrow() - }) - }) - - describe('getLatestKey()', () => { - it('returns undefined if no key history', () => { - const util = KeyStorageUtil.getKeyStorageUtil() - expect(util.getLatestKey('streamId')).toBe(undefined) - }) - - it('returns key passed in constructor', () => { - const lastKey = crypto.randomBytes(32) - const util = KeyStorageUtil.getKeyStorageUtil({ - streamId: { - groupKey: lastKey, - start: 7 - } - }) - - expect(util.getLatestKey('streamId')).toStrictEqual({ - groupKey: lastKey, - start: 7, - }) - }) - - it('returns the last key', () => { - const util = KeyStorageUtil.getKeyStorageUtil() - util.addKey('streamId', crypto.randomBytes(32), 1) - util.addKey('streamId', crypto.randomBytes(32), 5) - const lastKey = crypto.randomBytes(32) - util.addKey('streamId', lastKey, 7) - - expect(util.getLatestKey('streamId')).toStrictEqual({ - groupKey: lastKey, - start: 7, - }) - }) - }) - - describe('getKeysBetween()', () => { - it('returns empty array for wrong streamId', () => { - const util = KeyStorageUtil.getKeyStorageUtil() - expect(util.getKeysBetween('wrong-streamId', 1, 2)).toStrictEqual([]) - }) - - it('returns empty array when end time is before start of first key', () => { - const util = KeyStorageUtil.getKeyStorageUtil() - util.addKey('streamId', crypto.randomBytes(32), 10) - expect(util.getKeysBetween('streamId', 1, 9)).toStrictEqual([]) - }) - - it('returns only the latest key when start time is after last key', () => { - const util = KeyStorageUtil.getKeyStorageUtil() - util.addKey('streamId', crypto.randomBytes(32), 5) - const lastKey = crypto.randomBytes(32) - util.addKey('streamId', lastKey, 10) - expect(util.getKeysBetween('streamId', 15, 120)).toStrictEqual([{ - groupKey: lastKey, - start: 10 - }]) - }) - - it('returns keys in interval start-end', () => { - const util = KeyStorageUtil.getKeyStorageUtil() - const key1 = crypto.randomBytes(32) - const key2 = crypto.randomBytes(32) - const key3 = crypto.randomBytes(32) - const key4 = crypto.randomBytes(32) - const key5 = crypto.randomBytes(32) - - util.addKey('streamId', key1, 10) - util.addKey('streamId', key2, 20) - util.addKey('streamId', key3, 30) - util.addKey('streamId', key4, 40) - util.addKey('streamId', key5, 50) - - const expectedKeys = [{ - groupKey: key2, - start: 20 - }, { - groupKey: key3, - start: 30 - }, { - groupKey: key4, - start: 40 - }] - - expect(util.getKeysBetween('streamId', 23, 47)).toStrictEqual(expectedKeys) - expect(util.getKeysBetween('streamId', 20, 40)).toStrictEqual(expectedKeys) - }) - }) -}) diff --git a/test/unit/LatestKeyStorageUtil.test.js b/test/unit/LatestKeyStorageUtil.test.js deleted file mode 100644 index 6af172947..000000000 --- a/test/unit/LatestKeyStorageUtil.test.js +++ /dev/null @@ -1,72 +0,0 @@ -import crypto from 'crypto' - -import KeyStorageUtil from '../../src/KeyStorageUtil' - -describe.skip('LatestKeyStorageUtil', () => { - describe('hasKey()', () => { - it('returns true iff there is a GroupKeyHistory for the stream', () => { - const util = KeyStorageUtil.getKeyStorageUtil({ - streamId: { - groupKey: crypto.randomBytes(32), - start: Date.now() - } - }, false) - expect(util.hasKey('streamId')).toBe(true) - expect(util.hasKey('wrong-streamId')).toBe(false) - }) - }) - - describe('addKey()', () => { - it('throws if adding an older key', () => { - const util = KeyStorageUtil.getKeyStorageUtil({ - streamId: { - groupKey: crypto.randomBytes(32), - start: Date.now() - } - }, false) - expect(() => { - util.addKey('streamId', crypto.randomBytes(32), 0) - }).toThrow() - }) - }) - - describe('getLatestKey()', () => { - it('returns undefined if no key', () => { - const util = KeyStorageUtil.getKeyStorageUtil({}, false) - expect(util.getLatestKey('streamId')).toBe(undefined) - }) - - it('returns key passed in constructor', () => { - const lastKey = crypto.randomBytes(32) - const util = KeyStorageUtil.getKeyStorageUtil({ - streamId: { - groupKey: lastKey, - start: 7 - } - }, false) - expect(util.getLatestKey('streamId')).toStrictEqual({ - groupKey: lastKey, - start: 7, - }) - }) - - it('returns the last key', () => { - const util = KeyStorageUtil.getKeyStorageUtil({}, false) - util.addKey('streamId', crypto.randomBytes(32), 1) - util.addKey('streamId', crypto.randomBytes(32), 5) - const lastKey = crypto.randomBytes(32) - util.addKey('streamId', lastKey, 7) - expect(util.getLatestKey('streamId')).toStrictEqual({ - groupKey: lastKey, - start: 7, - }) - }) - }) - - describe('getKeysBetween()', () => { - it('throws since historical keys are not stored', () => { - const util = KeyStorageUtil.getKeyStorageUtil({}, false) - expect(() => util.getKeysBetween('wrong-streamId', 1, 2)).toThrow() - }) - }) -}) diff --git a/test/unit/MessageCreationUtil.test.js b/test/unit/MessageCreationUtil.test.js index e0f5f2b6b..0616e8787 100644 --- a/test/unit/MessageCreationUtil.test.js +++ b/test/unit/MessageCreationUtil.test.js @@ -1,18 +1,11 @@ -import crypto from 'crypto' - import sinon from 'sinon' import { ethers } from 'ethers' import { MessageLayer } from 'streamr-client-protocol' -import uniqueId from 'lodash.uniqueid' import MessageCreationUtil from '../../src/MessageCreationUtil' import Stream from '../../src/rest/domain/Stream' -import KeyStorageUtil from '../../src/KeyStorageUtil' -import KeyExchangeUtil from '../../src/KeyExchangeUtil' -import InvalidGroupKeyRequestError from '../../src/errors/InvalidGroupKeyRequestError' const { StreamMessage, MessageID, MessageRef } = MessageLayer -const { getKeyExchangeStreamId } = KeyExchangeUtil describe('MessageCreationUtil', () => { const hashedUsername = '0x16F78A7D6317F102BBD95FC9A4F3FF2E3249287690B8BDAD6B7810F82B34ACE3'.toLowerCase() @@ -233,289 +226,4 @@ describe('MessageCreationUtil', () => { expect(streamMessage).toEqual(getStreamMessage(stream.id, ts, 0, null)) }) }) - - describe.skip('encryption', () => { - const pubMsg = { - foo: 'bar', - } - - const stream = new Stream(null, { - id: 'streamId', - partitions: 1, - }) - - let client - - beforeEach(() => { - client = { - options: { - auth: { - username: 'username', - }, - }, - signer: { - signStreamMessage: (streamMessage) => { - /* eslint-disable no-param-reassign */ - streamMessage.signatureType = StreamMessage.SIGNATURE_TYPES.ETH - streamMessage.signature = 'signature' - /* eslint-enable no-param-reassign */ - return Promise.resolve() - }, - }, - getUserInfo: () => Promise.resolve({ - username: 'username', - }), - getStream: sinon.stub().resolves(stream), - } - }) - - it('should create cleartext messages when no key is defined', async () => { - const msgCreationUtil = new MessageCreationUtil(client.options.auth, client.signer, client.getUserInfo(), client.getStream) - const msg = await msgCreationUtil.createStreamMessage(stream, pubMsg, Date.now()) - expect(msg.encryptionType).toBe(StreamMessage.ENCRYPTION_TYPES.NONE) - expect(msg.getParsedContent()).toEqual(pubMsg) - }) - - it('should create encrypted messages when key defined in constructor', async () => { - const key = crypto.randomBytes(32) - const keyStorageUtil = KeyStorageUtil.getKeyStorageUtil() - keyStorageUtil.addKey(stream.id, key) - - const msgCreationUtil = new MessageCreationUtil( - client.options.auth, client.signer, client.getUserInfo(), client.getStream, keyStorageUtil, - ) - const msg = await msgCreationUtil.createStreamMessage(stream, pubMsg, Date.now()) - expect(msg.encryptionType).toBe(StreamMessage.ENCRYPTION_TYPES.AES) - expect(msg.getSerializedContent().length).toBe(58) // 16*2 + 13*2 (hex string made of IV + msg of 13 chars) - }) - - it('should throw when using a key with a size smaller than 256 bits', (done) => { - const key = crypto.randomBytes(16) - const msgCreationUtil = new MessageCreationUtil(client.options.auth, client.signer, client.getUserInfo(), client.getStream) - msgCreationUtil.createStreamMessage(stream, pubMsg, Date.now(), null, key).catch((err) => { - expect(err.toString()).toBe('Error: Group key must have a size of 256 bits, not 128') - done() - }) - }) - - it('should create encrypted messages when key defined in createStreamMessage() and use the same key later', async () => { - const key = crypto.randomBytes(32) - const msgCreationUtil = new MessageCreationUtil(client.options.auth, client.signer, client.getUserInfo(), client.getStream) - const msg1 = await msgCreationUtil.createStreamMessage(stream, pubMsg, Date.now(), null, key) - expect(msg1.encryptionType).toBe(StreamMessage.ENCRYPTION_TYPES.AES) - expect(msg1.getSerializedContent().length).toBe(58) - const msg2 = await msgCreationUtil.createStreamMessage(stream, pubMsg, Date.now()) - expect(msg2.encryptionType).toBe(StreamMessage.ENCRYPTION_TYPES.AES) - expect(msg2.getSerializedContent().length).toBe(58) - // should use different IVs - expect(msg1.getSerializedContent().slice(0, 32)).not.toEqual(msg2.getSerializedContent().slice(0, 32)) - // should produce different ciphertexts even if same plaintexts and same key - expect(msg1.getSerializedContent().slice(32)).not.toEqual(msg2.getSerializedContent().slice(32)) - }) - - it('should update the key when redefined', async () => { - const key1 = crypto.randomBytes(32) - const key2 = crypto.randomBytes(32) - const msgCreationUtil = new MessageCreationUtil(client.options.auth, client.signer, client.getUserInfo(), client.getStream) - const msg1 = await msgCreationUtil.createStreamMessage(stream, pubMsg, Date.now(), null, key1) - expect(msg1.encryptionType).toBe(StreamMessage.ENCRYPTION_TYPES.AES) - expect(msg1.getSerializedContent().length).toBe(58) - const msg2 = await msgCreationUtil.createStreamMessage(stream, pubMsg, Date.now(), null, key2) - expect(msg2.encryptionType).toBe(StreamMessage.ENCRYPTION_TYPES.NEW_KEY_AND_AES) - expect(msg2.getSerializedContent().length).toBe(122)// 16*2 + 32*2 + 13*2 (IV + key of 32 bytes + msg of 13 chars) - }) - }) - - describe.skip('createGroupKeyRequest', () => { - const stream = new Stream(null, { - id: 'streamId', - partitions: 1, - }) - - const auth = { - username: 'username', - } - - it('should not be able to create unsigned group key request', async (done) => { - const util = new MessageCreationUtil(auth, null, () => Promise.resolve({ - username: 'username', - }), sinon.stub().resolves(stream)) - - await util.createGroupKeyRequest({ - messagePublisherAddress: 'publisherId', - streamId: 'streamId', - publicKey: 'rsaPublicKey', - start: 1354155, - end: 2344155, - }).catch((err) => { - expect(err.message).toBe('Cannot create unsigned group key request. Must authenticate with "privateKey" or "provider"') - done() - }) - }) - - it('creates correct group key request', async () => { - const signer = { - signStreamMessage: (streamMessage) => { - /* eslint-disable no-param-reassign */ - streamMessage.signatureType = StreamMessage.SIGNATURE_TYPES.ETH - streamMessage.signature = 'signature' - /* eslint-enable no-param-reassign */ - return Promise.resolve() - }, - } - - const util = new MessageCreationUtil(auth, signer, () => Promise.resolve({ - username: 'username', - }), sinon.stub().resolves(stream)) - - const streamMessage = await util.createGroupKeyRequest({ - messagePublisherAddress: 'publisherId', - streamId: 'streamId', - publicKey: 'rsaPublicKey', - start: 1354155, - end: 2344155, - }) - - expect(streamMessage.getStreamId()).toBe(getKeyExchangeStreamId('publisherId')) // sending to publisher's keyexchange stream - const content = streamMessage.getParsedContent() - expect(streamMessage.messageType).toBe(StreamMessage.MESSAGE_TYPES.GROUP_KEY_REQUEST) - expect(streamMessage.encryptionType).toBe(StreamMessage.ENCRYPTION_TYPES.NONE) - expect(content.streamId).toBe('streamId') - expect(content.publicKey).toBe('rsaPublicKey') - expect(content.range.start).toBe(1354155) - expect(content.range.end).toBe(2344155) - expect(streamMessage.signature).toBeTruthy() - }) - }) - - describe.skip('createGroupKeyResponse', () => { - const stream = new Stream(null, { - id: 'streamId', - partitions: 1, - }) - - const auth = { - username: 'username', - } - - it('should not be able to create unsigned group key response', async (done) => { - const util = new MessageCreationUtil(auth, null, () => Promise.resolve({ - username: 'username', - }), sinon.stub().resolves(stream)) - const requestId = uniqueId() - await util.createGroupKeyResponse({ - subscriberAddress: 'subscriberId', - streamId: 'streamId', - requestId, - encryptedGroupKeys: [{ - groupKey: 'group-key', - start: 34524, - }] - }).catch((err) => { - expect(err.message).toBe('Cannot create unsigned group key response. Must authenticate with "privateKey" or "provider"') - done() - }) - }) - - it.skip('creates correct group key response', async () => { - const signer = { - signStreamMessage: (streamMessage) => { - /* eslint-disable no-param-reassign */ - streamMessage.signatureType = StreamMessage.SIGNATURE_TYPES.ETH - streamMessage.signature = 'signature' - /* eslint-enable no-param-reassign */ - return Promise.resolve() - }, - } - - const util = new MessageCreationUtil(auth, signer, () => Promise.resolve({ - username: 'username', - }), sinon.stub().resolves(stream)) - - const requestId = uniqueId() - const streamMessage = await util.createGroupKeyResponse({ - subscriberAddress: 'subscriberId', - streamId: 'streamId', - requestId, - encryptedGroupKeys: [{ - groupKey: 'encrypted-group-key', - start: 34524, - }] - }) - - expect(streamMessage.getStreamId()).toBe(getKeyExchangeStreamId('subscriberId')) // sending to subscriber's keyexchange stream - const content = streamMessage.getParsedContent() - expect(streamMessage.messageType).toBe(StreamMessage.MESSAGE_TYPES.GROUP_KEY_RESPONSE) - expect(streamMessage.encryptionType).toBe(StreamMessage.ENCRYPTION_TYPES.RSA) - expect(content.streamId).toBe('streamId') - expect(content.requestId).toBe(requestId) - expect(content.keys).toStrictEqual([{ - groupKey: 'encrypted-group-key', - start: 34524, - }]) - expect(streamMessage.signature).toBeTruthy() - }) - }) - - describe.skip('createErrorMessage', () => { - const stream = new Stream(null, { - id: 'streamId', - partitions: 1, - }) - - const auth = { - username: 'username', - } - - it('should not be able to create unsigned error message', async (done) => { - const util = new MessageCreationUtil(auth, null, () => Promise.resolve({ - username: 'username', - }), sinon.stub().resolves(stream)) - - await util.createErrorMessage({ - keyExchangeStreamId: 'keyExchangeStreamId', - error: new Error(), - streamId: stream.id, - requestId: uniqueId('requestId'), - }).catch((err) => { - expect(err.message).toBe('Cannot create unsigned error message. Must authenticate with "privateKey" or "provider"') - done() - }) - }) - - it('creates correct group key response', async () => { - const signer = { - signStreamMessage: (streamMessage) => { - /* eslint-disable no-param-reassign */ - streamMessage.signatureType = StreamMessage.SIGNATURE_TYPES.ETH - streamMessage.signature = 'signature' - /* eslint-enable no-param-reassign */ - return Promise.resolve() - }, - } - - const util = new MessageCreationUtil(auth, signer, () => Promise.resolve({ - username: 'username', - }), sinon.stub().resolves(stream)) - - const requestId = uniqueId('requestId') - const streamMessage = await util.createErrorMessage({ - keyExchangeStreamId: 'keyExchangeStreamId', - error: new InvalidGroupKeyRequestError('invalid'), - streamId: stream.id, - requestId, - }) - - expect(streamMessage.getStreamId()).toBe('keyExchangeStreamId') // sending to subscriber's keyexchange stream - - const content = streamMessage.getParsedContent() - expect(streamMessage.messageType).toBe(StreamMessage.MESSAGE_TYPES.GROUP_KEY_ERROR_RESPONSE) - expect(streamMessage.encryptionType).toBe(StreamMessage.ENCRYPTION_TYPES.NONE) - expect(content.code).toBe('INVALID_GROUP_KEY_REQUEST') - expect(content.requestId).toBe(requestId) - expect(content.streamId).toBe(stream.id) - expect(content.message).toBe('invalid') - expect(streamMessage.signature).toBeTruthy() - }) - }) }) diff --git a/test/unit/RealTimeSubscription.test.js b/test/unit/RealTimeSubscription.test.js index c77972266..6df6f0a8b 100644 --- a/test/unit/RealTimeSubscription.test.js +++ b/test/unit/RealTimeSubscription.test.js @@ -1,10 +1,7 @@ -import crypto from 'crypto' - import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' import RealTimeSubscription from '../../src/RealTimeSubscription' -import EncryptionUtil from '../../src/EncryptionUtil' import Subscription from '../../src/Subscription' import AbstractSubscription from '../../src/AbstractSubscription' @@ -508,353 +505,6 @@ describe('RealTimeSubscription', () => { sub.handleBroadcastMessage(byeMsg, async () => true) }) - - describe('decryption', () => { - let sub - afterEach(() => { - sub.stop() - }) - - it('should read clear text content without trying to decrypt', (done) => { - const msg1 = createMsg(1, 0, null, 0, { - foo: 'bar', - }) - sub = new RealTimeSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: (content) => { - expect(content).toStrictEqual(msg1.getParsedContent()) - done() - }, - }) - return sub.handleBroadcastMessage(msg1, async () => true) - }) - - it('should decrypt encrypted content with the correct key', (done) => { - const groupKey = crypto.randomBytes(32) - const data = { - foo: 'bar', - } - const msg1 = createMsg(1, 0, null, 0, data) - EncryptionUtil.encryptStreamMessage(msg1, groupKey) - sub = new RealTimeSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: (content) => { - expect(content).toStrictEqual(msg1.getParsedContent()) - done() - }, - groupKeys: { - publisherId: groupKey, - } - }) - return sub.handleBroadcastMessage(msg1, async () => true) - }) - - it('should emit "groupKeyMissing" when not able to decrypt with the wrong key', (done) => { - const correctGroupKey = crypto.randomBytes(32) - const wrongGroupKey = crypto.randomBytes(32) - const msg1 = createMsg(1, 0, null, 0, { - foo: 'bar', - }) - EncryptionUtil.encryptStreamMessage(msg1, correctGroupKey) - sub = new RealTimeSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: () => {}, - groupKeys: { - publisherId: wrongGroupKey, - } - }) - sub.once('groupKeyMissing', (publisherId) => { - expect(publisherId).toBe(msg1.getPublisherId()) - done() - }) - return sub.handleBroadcastMessage(msg1, async () => true) - }) - - it('emits "groupKeyMissing" multiple times before response received', (done) => { - const correctGroupKey = crypto.randomBytes(32) - const wrongGroupKey = crypto.randomBytes(32) - let counter = 0 - const msg1 = createMsg(1, 0, null, 0, { - foo: 'bar', - }) - EncryptionUtil.encryptStreamMessage(msg1, correctGroupKey) - sub = new RealTimeSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: () => {}, - groupKeys: { - publisherId: wrongGroupKey, - }, - propagationTimeout: 200, - }) - sub.on('groupKeyMissing', (publisherId) => { - if (counter < 3) { - expect(publisherId).toBe(msg1.getPublisherId()) - counter += 1 - } else { - // fake group key response after 3 requests - sub.setGroupKeys(publisherId, [correctGroupKey]) - setTimeout(() => { - if (counter > 3) { - throw new Error('Sent additional group key request after response received.') - } - done() - }, 1000) - } - }) - return sub.handleBroadcastMessage(msg1, async () => true) - }) - - it('emits "groupKeyMissing" MAX_NB_GROUP_KEY_REQUESTS times before response received', (done) => { - const correctGroupKey = crypto.randomBytes(32) - const wrongGroupKey = crypto.randomBytes(32) - let counter = 0 - const msg1 = createMsg(1, 0, null, 0, { - foo: 'bar', - }) - const timeout = 200 - EncryptionUtil.encryptStreamMessage(msg1, correctGroupKey) - sub = new RealTimeSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: () => {}, - groupKeys: { - publisherId: wrongGroupKey, - }, - propagationTimeout: timeout, - }) - let t - sub.on('groupKeyMissing', (publisherId) => { - expect(publisherId).toBe(msg1.getPublisherId()) - counter += 1 - clearTimeout(t) - t = setTimeout(() => { - expect(counter).toBe(AbstractSubscription.MAX_NB_GROUP_KEY_REQUESTS) - done() - }, timeout * (AbstractSubscription.MAX_NB_GROUP_KEY_REQUESTS + 2)) - }) - return sub.handleBroadcastMessage(msg1, async () => true) - }) - - it('should queue messages when not able to decrypt and handle them once the key is updated', async () => { - const correctGroupKey = crypto.randomBytes(32) - const wrongGroupKey = crypto.randomBytes(32) - const data1 = { - test: 'data1', - } - const data2 = { - test: 'data2', - } - const msg1 = createMsg(1, 0, null, 0, data1) - const msg2 = createMsg(2, 0, 1, 0, data2) - EncryptionUtil.encryptStreamMessage(msg1, correctGroupKey) - EncryptionUtil.encryptStreamMessage(msg2, correctGroupKey) - let received1 = null - let received2 = null - sub = new RealTimeSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: (content) => { - if (!received1) { - received1 = content - } else { - received2 = content - } - }, - groupKeys: { - publisherId: wrongGroupKey, - }, - }) - // cannot decrypt msg1, queues it and emits "groupKeyMissing" (should send group key request). - await sub.handleBroadcastMessage(msg1, async () => true) - // cannot decrypt msg2, queues it. - await sub.handleBroadcastMessage(msg2, async () => true) - // faking the reception of the group key response - sub.setGroupKeys('publisherId', [correctGroupKey]) - // try again to decrypt the queued messages but this time with the correct key - expect(received1).toStrictEqual(data1) - expect(received2).toStrictEqual(data2) - }) - - it('should queue messages when not able to decrypt and handle them once the keys are updated (multiple publishers)', async () => { - const groupKey1 = crypto.randomBytes(32) - const groupKey2 = crypto.randomBytes(32) - const wrongGroupKey = crypto.randomBytes(32) - const data1 = { - test: 'data1', - } - const data2 = { - test: 'data2', - } - const data3 = { - test: 'data3', - } - const data4 = { - test: 'data4', - } - const msg1 = createMsg(1, 0, null, 0, data1, 'publisherId1') - const msg2 = createMsg(2, 0, 1, 0, data2, 'publisherId1') - const msg3 = createMsg(1, 0, null, 0, data3, 'publisherId2') - const msg4 = createMsg(2, 0, 1, 0, data4, 'publisherId2') - EncryptionUtil.encryptStreamMessage(msg1, groupKey1) - EncryptionUtil.encryptStreamMessage(msg2, groupKey1) - EncryptionUtil.encryptStreamMessage(msg3, groupKey2) - EncryptionUtil.encryptStreamMessage(msg4, groupKey2) - const received = [] - sub = new RealTimeSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: (content) => { - received.push(content) - }, - groupKeys: { - publisherId: wrongGroupKey, - }, - }) - // cannot decrypt msg1, queues it and emits "groupKeyMissing" (should send group key request). - await sub.handleBroadcastMessage(msg1, async () => true) - // cannot decrypt msg2, queues it. - await sub.handleBroadcastMessage(msg2, async () => true) - // cannot decrypt msg3, queues it and emits "groupKeyMissing" (should send group key request). - await sub.handleBroadcastMessage(msg3, async () => true) - // cannot decrypt msg4, queues it. - await sub.handleBroadcastMessage(msg4, async () => true) - // faking the reception of the group key response - sub.setGroupKeys('publisherId2', [groupKey2]) - sub.setGroupKeys('publisherId1', [groupKey1]) - // try again to decrypt the queued messages but this time with the correct key - expect(received[0]).toStrictEqual(data3) - expect(received[1]).toStrictEqual(data4) - expect(received[2]).toStrictEqual(data1) - expect(received[3]).toStrictEqual(data2) - }) - - it('should queue messages when cannot decrypt and handle them once the keys are updated (multiple publishers interleaved)', async () => { - const groupKey1 = crypto.randomBytes(32) - const groupKey2 = crypto.randomBytes(32) - const wrongGroupKey = crypto.randomBytes(32) - const data1 = { - test: 'data1', - } - const data2 = { - test: 'data2', - } - const data3 = { - test: 'data3', - } - const data4 = { - test: 'data4', - } - const data5 = { - test: 'data5', - } - const msg1Pub1 = createMsg(1, 0, null, 0, data1, 'publisherId1') - const msg2Pub1 = createMsg(2, 0, 1, 0, data2, 'publisherId1') - const msg3Pub1 = createMsg(3, 0, 2, 0, data3, 'publisherId1') - const msg1Pub2 = createMsg(1, 0, null, 0, data4, 'publisherId2') - const msg2Pub2 = createMsg(2, 0, 1, 0, data5, 'publisherId2') - EncryptionUtil.encryptStreamMessage(msg1Pub1, groupKey1) - EncryptionUtil.encryptStreamMessage(msg2Pub1, groupKey1) - EncryptionUtil.encryptStreamMessage(msg1Pub2, groupKey2) - EncryptionUtil.encryptStreamMessage(msg2Pub2, groupKey2) - const received = [] - sub = new RealTimeSubscription({ - streamId: msg1Pub1.getStreamId(), - streamPartition: msg1Pub1.getStreamPartition(), - callback: (content) => { - received.push(content) - }, - groupKeys: { - publisherId: wrongGroupKey, - }, - }) - await sub.handleBroadcastMessage(msg1Pub1, async () => true) - await sub.handleBroadcastMessage(msg1Pub2, async () => true) - await sub.handleBroadcastMessage(msg2Pub1, async () => true) - sub.setGroupKeys('publisherId1', [groupKey1]) - await sub.handleBroadcastMessage(msg3Pub1, async () => true) - await sub.handleBroadcastMessage(msg2Pub2, async () => true) - sub.setGroupKeys('publisherId2', [groupKey2]) - - // try again to decrypt the queued messages but this time with the correct key - expect(received[0]).toStrictEqual(data1) - expect(received[1]).toStrictEqual(data2) - expect(received[2]).toStrictEqual(data3) - expect(received[3]).toStrictEqual(data4) - expect(received[4]).toStrictEqual(data5) - }) - - it('should call "onUnableToDecrypt" when not able to decrypt for the second time', async () => { - const correctGroupKey = crypto.randomBytes(32) - const wrongGroupKey = crypto.randomBytes(32) - const otherWrongGroupKey = crypto.randomBytes(32) - const msg1 = createMsg(1, 0, null, 0, { - test: 'data1', - }) - const msg2 = createMsg(2, 0, 1, 0, { - test: 'data2', - }) - EncryptionUtil.encryptStreamMessage(msg1, correctGroupKey) - EncryptionUtil.encryptStreamMessage(msg2, correctGroupKey) - let undecryptableMsg = null - sub = new RealTimeSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: () => { - throw new Error('should not call the handler') - }, - groupKeys: { - publisherId: wrongGroupKey, - }, - onUnableToDecrypt: (error) => { - undecryptableMsg = error.streamMessage - } - }) - // cannot decrypt msg1, emits "groupKeyMissing" (should send group key request). - await sub.handleBroadcastMessage(msg1, async () => true) - // cannot decrypt msg2, queues it. - await sub.handleBroadcastMessage(msg2, async () => true) - // faking the reception of the group key response - sub.setGroupKeys('publisherId', [otherWrongGroupKey]) - expect(undecryptableMsg).toStrictEqual(msg2) - }) - - it('should decrypt first content, update key and decrypt second content', async (done) => { - const groupKey1 = crypto.randomBytes(32) - const groupKey2 = crypto.randomBytes(32) - const data1 = { - test: 'data1', - } - const data2 = { - test: 'data2', - } - const msg1 = createMsg(1, 0, null, 0, data1) - const msg2 = createMsg(2, 0, 1, 0, data2) - EncryptionUtil.encryptStreamMessageAndNewKey(groupKey2, msg1, groupKey1) - EncryptionUtil.encryptStreamMessage(msg2, groupKey2) - let test1Ok = false - sub = new RealTimeSubscription({ - streamId: msg1.getStreamId(), - streamPartition: msg1.getStreamPartition(), - callback: (content) => { - if (JSON.stringify(content) === JSON.stringify(data1)) { - expect(sub.groupKeys[msg1.getPublisherId().toLowerCase()]).toStrictEqual(groupKey2) - test1Ok = true - } else if (test1Ok && JSON.stringify(content) === JSON.stringify(data2)) { - done() - } - }, - groupKeys: { - publisherId: groupKey1, - }, - }) - await sub.handleBroadcastMessage(msg1, async () => true) - return sub.handleBroadcastMessage(msg2, async () => true) - }) - }) }) describe('handleError()', () => { diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index a36997307..8f6ae0e57 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -1,5 +1,3 @@ -import crypto from 'crypto' - import EventEmitter from 'eventemitter3' import sinon from 'sinon' import debug from 'debug' @@ -10,7 +8,6 @@ import { wait } from 'streamr-test-utils' import FailedToPublishError from '../../src/errors/FailedToPublishError' import Connection from '../../src/Connection' import Subscription from '../../src/Subscription' -import KeyExchangeUtil from '../../src/KeyExchangeUtil' // import StreamrClient from '../../src/StreamrClient' import { uid } from '../utils' @@ -35,7 +32,6 @@ const { } = ControlLayer const { StreamMessage, MessageRef, MessageID, MessageIDStrict } = MessageLayer -const { getKeyExchangeStreamId } = KeyExchangeUtil const mockDebug = debug('mock') describe('StreamrClient', () => { @@ -966,19 +962,6 @@ describe('StreamrClient', () => { }) }) - it.skip('sets the group keys if passed as arguments', () => { - const groupKey = crypto.randomBytes(32) - const sub = client.subscribe({ - stream: 'stream1', - groupKeys: { - publisherId: groupKey - } - }, () => {}) - expect(client.options.subscriberGroupKeys).toHaveProperty('stream1.publisherId.start') - expect(client.options.subscriberGroupKeys.stream1.publisherId.groupKey).toEqual(groupKey) - expect(sub.groupKeys['publisherId'.toLowerCase()]).toEqual(groupKey) - }) - it('sends a subscribe request for a given partition', (done) => { const sub = mockSubscription({ stream: 'stream1', @@ -1511,7 +1494,7 @@ describe('StreamrClient', () => { expect(c.options.auth.apiKey).toBeTruthy() }) - it('sets private key with 0x prefix', (done) => { + it.skip('sets private key with 0x prefix', (done) => { connection = createConnectionMock() const c = new StubbedStreamrClient({ auth: { @@ -1525,7 +1508,7 @@ describe('StreamrClient', () => { c.once('connected', async () => { await wait() expect(requests[0]).toEqual(new SubscribeRequest({ - streamId: getKeyExchangeStreamId('0x650EBB201f635652b44E4afD1e0193615922381D'), + //streamId: getKeyExchangeStreamId('0x650EBB201f635652b44E4afD1e0193615922381D'), streamPartition: 0, sessionToken, requestId: requests[0].requestId, @@ -1539,82 +1522,6 @@ describe('StreamrClient', () => { const c = new StubbedStreamrClient({}, createConnectionMock()) expect(c.session.options.unauthenticated).toBeTruthy() }) - - describe.skip('groupKeys', () => { - it('sets start time of group key', () => { - const groupKey = crypto.randomBytes(32) - const c = new StubbedStreamrClient({ - subscriberGroupKeys: { - streamId: { - publisherId: groupKey - } - } - }, createConnectionMock()) - expect(c.options.subscriberGroupKeys.streamId.publisherId.groupKey).toBe(groupKey) - expect(c.options.subscriberGroupKeys.streamId.publisherId.start).toBeTruthy() - }) - - it('keeps start time passed in the constructor', () => { - const groupKey = crypto.randomBytes(32) - const c = new StubbedStreamrClient({ - subscriberGroupKeys: { - streamId: { - publisherId: { - groupKey, - start: 12 - } - } - } - }, createConnectionMock()) - expect(c.options.subscriberGroupKeys.streamId.publisherId.groupKey).toBe(groupKey) - expect(c.options.subscriberGroupKeys.streamId.publisherId.start).toBe(12) - }) - - it('updates the latest group key with a more recent key', () => { - const c = new StubbedStreamrClient({ - subscriberGroupKeys: { - streamId: { - publisherId: crypto.randomBytes(32) - } - } - }, createConnectionMock()) - c.subscribedStreamPartitions = { - streamId0: { - setSubscriptionsGroupKeys: sinon.stub() - } - } - const newGroupKey = { - groupKey: crypto.randomBytes(32), - start: Date.now() + 2000 - } - // eslint-disable-next-line no-underscore-dangle - c._setGroupKeys('streamId', 'publisherId', [newGroupKey]) - expect(c.options.subscriberGroupKeys.streamId.publisherId).toBe(newGroupKey) - }) - - it('does not update the latest group key with an older key', () => { - const groupKey = crypto.randomBytes(32) - const c = new StubbedStreamrClient({ - subscriberGroupKeys: { - streamId: { - publisherId: groupKey - } - } - }, createConnectionMock()) - c.subscribedStreamPartitions = { - streamId0: { - setSubscriptionsGroupKeys: sinon.stub() - } - } - const oldGroupKey = { - groupKey: crypto.randomBytes(32), - start: Date.now() - 2000 - } - // eslint-disable-next-line no-underscore-dangle - c._setGroupKeys('streamId', 'publisherId', [oldGroupKey]) - expect(c.options.subscriberGroupKeys.streamId.publisherId.groupKey).toBe(groupKey) - }) - }) }) describe('StreamrClient.generateEthereumAccount()', () => { diff --git a/test/unit/SubscribedStreamPartition.test.js b/test/unit/SubscribedStreamPartition.test.js index 1c0bb416a..f31b0539f 100644 --- a/test/unit/SubscribedStreamPartition.test.js +++ b/test/unit/SubscribedStreamPartition.test.js @@ -310,24 +310,5 @@ describe('SubscribedStreamPartition', () => { it('should return true', () => { expect(subscribedStreamPartition.emptySubscriptionsSet()).toBe(true) }) - - it.skip('should call setGroupKeys() and checkQueue() for every subscription', async () => { - const sub2 = { - id: 'sub2Id', - setGroupKeys: sinon.stub(), - } - const sub3 = { - id: 'sub3Id', - setGroupKeys: sinon.stub(), - } - - subscribedStreamPartition.removeSubscription(sub1) - subscribedStreamPartition.addSubscription(sub2) - subscribedStreamPartition.addSubscription(sub3) - - await subscribedStreamPartition.setSubscriptionsGroupKeys('publisherId', ['group-key-1', 'group-key-2']) - expect(sub2.setGroupKeys.calledWith('publisherId', ['group-key-1', 'group-key-2'])).toBeTruthy() - expect(sub3.setGroupKeys.calledWith('publisherId', ['group-key-1', 'group-key-2'])).toBeTruthy() - }) }) }) From 6bdbd78d4df34dbb8bf574e353d0da258514ef19 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 11 Aug 2020 12:51:21 -0400 Subject: [PATCH 005/517] Move some methods around. --- src/StreamrClient.js | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/StreamrClient.js b/src/StreamrClient.js index baa9e6971..e2d117d3b 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -401,6 +401,17 @@ export default class StreamrClient extends EventEmitter { ) } + _requestPublish(streamMessage, sessionToken) { + const requestId = this.resendUtil.generateRequestId() + const request = new ControlLayer.PublishRequest({ + streamMessage, + requestId, + sessionToken, + }) + this.debug('_requestPublish: %o', request) + return this.connection.send(request) + } + async resend(optionsOrStreamId, callback) { const options = this._validateParameters(optionsOrStreamId, callback) @@ -794,22 +805,6 @@ export default class StreamrClient extends EventEmitter { } } - async publishStreamMessage(streamMessage) { - const sessionToken = await this.session.getSessionToken() - return this._requestPublish(streamMessage, sessionToken) - } - - _requestPublish(streamMessage, sessionToken) { - const requestId = this.resendUtil.generateRequestId() - const request = new ControlLayer.PublishRequest({ - streamMessage, - requestId, - sessionToken, - }) - this.debug('_requestPublish: %o', request) - return this.connection.send(request) - } - handleError(msg) { this.debug(msg) this.emit('error', msg) From 699df0d9887ef837024f88a021324b41b4db7859 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 11 Aug 2020 13:12:51 -0400 Subject: [PATCH 006/517] Refactor publishing logic into src/Publisher. --- src/Publisher.js | 145 ++++++++++++++++++++++++++++++++ src/StreamrClient.js | 109 +++--------------------- test/unit/StreamrClient.test.js | 2 +- 3 files changed, 156 insertions(+), 100 deletions(-) create mode 100644 src/Publisher.js diff --git a/src/Publisher.js b/src/Publisher.js new file mode 100644 index 000000000..81f6fce45 --- /dev/null +++ b/src/Publisher.js @@ -0,0 +1,145 @@ +import EventEmitter from 'eventemitter3' +import debugFactory from 'debug' +import qs from 'qs' +import once from 'once' +import { Wallet } from 'ethers' +import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' +import uniqueId from 'lodash.uniqueid' + +import HistoricalSubscription from './HistoricalSubscription' +import Connection from './Connection' +import Session from './Session' +import Signer from './Signer' +import SubscribedStreamPartition from './SubscribedStreamPartition' +import Stream from './rest/domain/Stream' +import FailedToPublishError from './errors/FailedToPublishError' +import MessageCreationUtil from './MessageCreationUtil' +import { waitFor, getVersionString } from './utils' +import RealTimeSubscription from './RealTimeSubscription' +import CombinedSubscription from './CombinedSubscription' +import Subscription from './Subscription' +import ResendUtil from './ResendUtil' + +function getStreamId(streamObjectOrId) { + if (streamObjectOrId instanceof Stream) { + return streamObjectOrId.id + } + + if (typeof streamObjectOrId === 'string') { + return streamObjectOrId + } + + throw new Error(`First argument must be a Stream object or the stream id! Was: ${streamObjectOrId}`) +} + +export default class Publisher { + constructor(client) { + this.client = client + this.publishQueue = [] + this.signer = Signer.createSigner({ + ...client.options.auth, + debug: client.debug, + }, client.options.publishWithSignature) + + this.debug = client.debug.extend('Publisher') + + if (client.session.isUnauthenticated()) { + this.msgCreationUtil = null + } else { + this.msgCreationUtil = new MessageCreationUtil( + client.options.auth, this.signer, once(() => client.getUserInfo()), + (streamId) => client.getStream(streamId) + .catch((err) => client.emit('error', err)) + ) + } + + // On connect/reconnect, send pending subscription requests + this.onConnected = this.onConnected.bind(this) + client.connection.on('connected', this.onConnected) + } + + async onConnected() { + if (!this.client.isConnected()) { return } + try { + // Check pending publish requests + const publishQueueCopy = this.publishQueue.slice(0) + this.publishQueue = [] + publishQueueCopy.forEach((publishFn) => publishFn()) + } catch (err) { + this.client.emit('error', err) + } + } + + async publish(streamObjectOrId, data, timestamp = new Date(), partitionKey = null) { + if (this.client.session.isUnauthenticated()) { + throw new Error('Need to be authenticated to publish.') + } + // Validate streamObjectOrId + const streamId = getStreamId(streamObjectOrId) + + const timestampAsNumber = timestamp instanceof Date ? timestamp.getTime() : new Date(timestamp).getTime() + const [sessionToken, streamMessage] = await Promise.all([ + this.client.session.getSessionToken(), + this.msgCreationUtil.createStreamMessage(streamObjectOrId, data, timestampAsNumber, partitionKey), + ]) + + if (this.client.isConnected()) { + // If connected, emit a publish request + return this._requestPublish(streamMessage, sessionToken) + } + + if (this.client.options.autoConnect) { + if (this.publishQueue.length >= this.client.options.maxPublishQueueSize) { + throw new FailedToPublishError( + streamId, + data, + `publishQueue exceeded maxPublishQueueSize=${this.options.maxPublishQueueSize}`, + ) + } + + const published = new Promise((resolve, reject) => { + this.publishQueue.push(async () => { + let publishRequest + try { + publishRequest = await this._requestPublish(streamMessage, sessionToken) + } catch (err) { + reject(err) + this.client.emit('error', err) + return + } + resolve(publishRequest) + }) + }) + // be sure to trigger connection *after* queueing publish + await this.client.ensureConnected() // await to ensure connection error fails publish + return published + } + + throw new FailedToPublishError( + streamId, + data, + 'Wait for the "connected" event before calling publish, or set autoConnect to true!', + ) + } + + _requestPublish(streamMessage, sessionToken) { + const requestId = this.client.resendUtil.generateRequestId() + const request = new ControlLayer.PublishRequest({ + streamMessage, + requestId, + sessionToken, + }) + this.debug('_requestPublish: %o', request) + return this.client.connection.send(request) + } + + getPublisherId() { + return this.msgCreationUtil.getPublisherId() + } + + stop() { + if (this.msgCreationUtil) { + this.msgCreationUtil.stop() + } + } +} diff --git a/src/StreamrClient.js b/src/StreamrClient.js index e2d117d3b..a1c5f71e4 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -19,6 +19,7 @@ import RealTimeSubscription from './RealTimeSubscription' import CombinedSubscription from './CombinedSubscription' import Subscription from './Subscription' import ResendUtil from './ResendUtil' +import Publisher from './Publisher' const { SubscribeRequest, @@ -94,27 +95,12 @@ export default class StreamrClient extends EventEmitter { this.options.auth.privateKey = `0x${this.options.auth.privateKey}` } - this.publishQueue = [] this.session = new Session(this, this.options.auth) - this.signer = Signer.createSigner({ - ...this.options.auth, - debug: this.debug, - }, this.options.publishWithSignature) // Event handling on connection object this.connection = connection || new Connection(this.options) this.getUserInfo = this.getUserInfo.bind(this) - if (this.session.isUnauthenticated()) { - this.msgCreationUtil = null - } else { - this.msgCreationUtil = new MessageCreationUtil( - this.options.auth, this.signer, once(() => this.getUserInfo()), - (streamId) => this.getStream(streamId) - .catch((err) => this.emit('error', err)), this.keyStorageUtil, - ) - } - this.resendUtil = new ResendUtil() this.resendUtil.on('error', (err) => this.emit('error', err)) @@ -232,11 +218,6 @@ export default class StreamrClient extends EventEmitter { } }) }) - - // Check pending publish requests - const publishQueueCopy = this.publishQueue.slice(0) - this.publishQueue = [] - publishQueueCopy.forEach((publishFn) => publishFn()) } catch (err) { this.emit('error', err) } @@ -276,6 +257,8 @@ export default class StreamrClient extends EventEmitter { this.emit('error', errorObject) } }) + + this.publisher = new Publisher(this) } /** @@ -342,76 +325,6 @@ export default class StreamrClient extends EventEmitter { return subs } - async publish(streamObjectOrId, data, timestamp = new Date(), partitionKey = null) { - if (this.session.isUnauthenticated()) { - throw new Error('Need to be authenticated to publish.') - } - // Validate streamObjectOrId - let streamId - if (streamObjectOrId instanceof Stream) { - streamId = streamObjectOrId.id - } else if (typeof streamObjectOrId === 'string') { - streamId = streamObjectOrId - } else { - throw new Error(`First argument must be a Stream object or the stream id! Was: ${streamObjectOrId}`) - } - - const timestampAsNumber = timestamp instanceof Date ? timestamp.getTime() : new Date(timestamp).getTime() - const [sessionToken, streamMessage] = await Promise.all([ - this.session.getSessionToken(), - this.msgCreationUtil.createStreamMessage(streamObjectOrId, data, timestampAsNumber, partitionKey), - ]) - - if (this.isConnected()) { - // If connected, emit a publish request - return this._requestPublish(streamMessage, sessionToken) - } - - if (this.options.autoConnect) { - if (this.publishQueue.length >= this.options.maxPublishQueueSize) { - throw new FailedToPublishError( - streamId, - data, - `publishQueue exceeded maxPublishQueueSize=${this.options.maxPublishQueueSize}`, - ) - } - - const published = new Promise((resolve, reject) => { - this.publishQueue.push(async () => { - let publishRequest - try { - publishRequest = await this._requestPublish(streamMessage, sessionToken) - } catch (err) { - reject(err) - this.emit('error', err) - return - } - resolve(publishRequest) - }) - }) - // be sure to trigger connection *after* queueing publish - await this.ensureConnected() // await to ensure connection error fails publish - return published - } - - throw new FailedToPublishError( - streamId, - data, - 'Wait for the "connected" event before calling publish, or set autoConnect to true!', - ) - } - - _requestPublish(streamMessage, sessionToken) { - const requestId = this.resendUtil.generateRequestId() - const request = new ControlLayer.PublishRequest({ - streamMessage, - requestId, - sessionToken, - }) - this.debug('_requestPublish: %o', request) - return this.connection.send(request) - } - async resend(optionsOrStreamId, callback) { const options = this._validateParameters(optionsOrStreamId, callback) @@ -619,10 +532,7 @@ export default class StreamrClient extends EventEmitter { } disconnect() { - if (this.msgCreationUtil) { - this.msgCreationUtil.stop() - } - + this.publisher.stop() this.subscribedStreamPartitions = {} return this.connection.disconnect() } @@ -631,8 +541,12 @@ export default class StreamrClient extends EventEmitter { return this.session.logout() } + async publish(...args) { + return this.publisher.publish(...args) + } + getPublisherId() { - return this.msgCreationUtil.getPublisherId() + return this.publisher.getPublisherId() } /** @@ -658,10 +572,7 @@ export default class StreamrClient extends EventEmitter { async ensureDisconnected() { this.connection.clearReconnectTimeout() - if (this.msgCreationUtil) { - this.msgCreationUtil.stop() - } - + this.publisher.stop() if (this.isDisconnected()) { return } if (this.isDisconnecting()) { diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index 8f6ae0e57..cbaf5ec19 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -1343,7 +1343,7 @@ describe('StreamrClient', () => { describe('publish', () => { function getPublishRequest(content, streamId, timestamp, seqNum, prevMsgRef, requestId) { - const messageId = new MessageID(streamId, 0, timestamp, seqNum, StubbedStreamrClient.hashedUsername, client.msgCreationUtil.msgChainId) + const messageId = new MessageID(streamId, 0, timestamp, seqNum, StubbedStreamrClient.hashedUsername, client.publisher.msgCreationUtil.msgChainId) const streamMessage = new StreamMessage({ messageId, prevMsgRef, From 095705e71610cdcb3d42a9720934524cf768e057 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 12 Aug 2020 10:38:15 -0400 Subject: [PATCH 007/517] Tidy tests and code for src/Publisher. --- src/Publisher.js | 16 +--------------- src/StreamrClient.js | 4 ---- test/integration/StreamEndpoints.test.js | 8 ++++---- test/integration/StreamrClient.test.js | 4 ++-- 4 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/Publisher.js b/src/Publisher.js index 81f6fce45..b2ab46c6d 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -1,24 +1,10 @@ -import EventEmitter from 'eventemitter3' -import debugFactory from 'debug' -import qs from 'qs' import once from 'once' -import { Wallet } from 'ethers' -import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' -import uniqueId from 'lodash.uniqueid' +import { ControlLayer } from 'streamr-client-protocol' -import HistoricalSubscription from './HistoricalSubscription' -import Connection from './Connection' -import Session from './Session' import Signer from './Signer' -import SubscribedStreamPartition from './SubscribedStreamPartition' import Stream from './rest/domain/Stream' import FailedToPublishError from './errors/FailedToPublishError' import MessageCreationUtil from './MessageCreationUtil' -import { waitFor, getVersionString } from './utils' -import RealTimeSubscription from './RealTimeSubscription' -import CombinedSubscription from './CombinedSubscription' -import Subscription from './Subscription' -import ResendUtil from './ResendUtil' function getStreamId(streamObjectOrId) { if (streamObjectOrId instanceof Stream) { diff --git a/src/StreamrClient.js b/src/StreamrClient.js index a1c5f71e4..7f7ecb5d4 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -9,11 +9,7 @@ import uniqueId from 'lodash.uniqueid' import HistoricalSubscription from './HistoricalSubscription' import Connection from './Connection' import Session from './Session' -import Signer from './Signer' import SubscribedStreamPartition from './SubscribedStreamPartition' -import Stream from './rest/domain/Stream' -import FailedToPublishError from './errors/FailedToPublishError' -import MessageCreationUtil from './MessageCreationUtil' import { waitFor, getVersionString } from './utils' import RealTimeSubscription from './RealTimeSubscription' import CombinedSubscription from './CombinedSubscription' diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.js index a63971abd..dc5bae1b5 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.js @@ -117,13 +117,13 @@ describe('StreamEndpoints', () => { describe('getStreamPublishers', () => { it('retrieves a list of publishers', async () => { const publishers = await client.getStreamPublishers(createdStream.id) - assert.deepStrictEqual(publishers, [client.signer.address.toLowerCase()]) + assert.deepStrictEqual(publishers, [client.publisher.signer.address.toLowerCase()]) }) }) describe('isStreamPublisher', () => { it('returns true for valid publishers', async () => { - const valid = await client.isStreamPublisher(createdStream.id, client.signer.address.toLowerCase()) + const valid = await client.isStreamPublisher(createdStream.id, client.publisher.signer.address.toLowerCase()) assert(valid) }) it('returns false for invalid publishers', async () => { @@ -135,13 +135,13 @@ describe('StreamEndpoints', () => { describe('getStreamSubscribers', () => { it('retrieves a list of publishers', async () => { const subscribers = await client.getStreamSubscribers(createdStream.id) - assert.deepStrictEqual(subscribers, [client.signer.address.toLowerCase()]) + assert.deepStrictEqual(subscribers, [client.publisher.signer.address.toLowerCase()]) }) }) describe('isStreamSubscriber', () => { it('returns true for valid subscribers', async () => { - const valid = await client.isStreamSubscriber(createdStream.id, client.signer.address.toLowerCase()) + const valid = await client.isStreamSubscriber(createdStream.id, client.publisher.signer.address.toLowerCase()) assert(valid) }) it('returns false for invalid subscribers', async () => { diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index 549fb590b..5424e5972 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -782,7 +782,7 @@ describe('StreamrClient', () => { const subStream = client._getSubscribedStreamPartition(stream.id, 0) // eslint-disable-line no-underscore-dangle const publishers = await subStream.getPublishers() const map = {} - map[client.signer.address.toLowerCase()] = true + map[client.publisher.signer.address.toLowerCase()] = true assert.deepStrictEqual(publishers, map) assert.strictEqual(streamMessage.signatureType, StreamMessage.SIGNATURE_TYPES.ETH) assert(streamMessage.getPublisherId()) @@ -824,7 +824,7 @@ describe('StreamrClient', () => { const subStream = client._getSubscribedStreamPartition(stream.id, 0) // eslint-disable-line no-underscore-dangle const publishers = await subStream.getPublishers() const map = {} - map[client.signer.address.toLowerCase()] = true + map[client.publisher.signer.address.toLowerCase()] = true assert.deepStrictEqual(publishers, map) assert.strictEqual(streamMessage.signatureType, StreamMessage.SIGNATURE_TYPES.ETH) assert(streamMessage.getPublisherId()) From d785096fa07e96ae15ab3d7ebbe13a70b63c9473 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 12 Aug 2020 11:01:47 -0400 Subject: [PATCH 008/517] Remove unnecessary line from AbstractSubscription. --- src/AbstractSubscription.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/AbstractSubscription.js b/src/AbstractSubscription.js index e201f122c..94878579d 100644 --- a/src/AbstractSubscription.js +++ b/src/AbstractSubscription.js @@ -22,7 +22,6 @@ export default class AbstractSubscription extends Subscription { resendTimeout, debug, }) - this.callback = callback this.pendingResendRequestIds = {} this._lastMessageHandlerPromise = {} this.orderingUtil = (orderMessages) ? new OrderingUtil(streamId, streamPartition, (orderedMessage) => { From 725f3f36673e5f2034e05a5a9afd42cae25429f7 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 12 Aug 2020 11:09:04 -0400 Subject: [PATCH 009/517] Refactor resends logic into src/Resender. --- src/Publisher.js | 2 +- src/Resender.js | 155 ++++++++++++++++++++++++++++++++++++++ src/StreamrClient.js | 173 +++---------------------------------------- 3 files changed, 166 insertions(+), 164 deletions(-) create mode 100644 src/Resender.js diff --git a/src/Publisher.js b/src/Publisher.js index b2ab46c6d..8cb5d49a6 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -109,7 +109,7 @@ export default class Publisher { } _requestPublish(streamMessage, sessionToken) { - const requestId = this.client.resendUtil.generateRequestId() + const requestId = this.client.resender.resendUtil.generateRequestId() const request = new ControlLayer.PublishRequest({ streamMessage, requestId, diff --git a/src/Resender.js b/src/Resender.js new file mode 100644 index 000000000..80656a41f --- /dev/null +++ b/src/Resender.js @@ -0,0 +1,155 @@ +import once from 'once' +import { ControlLayer, MessageLayer } from 'streamr-client-protocol' + +import HistoricalSubscription from './HistoricalSubscription' +import Subscription from './Subscription' +import ResendUtil from './ResendUtil' + +const { ResendLastRequest, ResendFromRequest, ResendRangeRequest, ControlMessage } = ControlLayer + +const { MessageRef } = MessageLayer + +export default class Resender { + constructor(client) { + this.client = client + this.resendUtil = new ResendUtil() + this.resendUtil.on('error', (err) => this.client.emit('error', err)) + this.debug = client.debug.extend('Resends') + + // Unicast messages to a specific subscription only + this.client.connection.on(ControlMessage.TYPES.UnicastMessage, async (msg) => { + const stream = this.client._getSubscribedStreamPartition(msg.streamMessage.getStreamId(), msg.streamMessage.getStreamPartition()) + if (stream) { + const sub = this.resendUtil.getSubFromResendResponse(msg) + + if (sub && stream.getSubscription(sub.id)) { + // sub.handleResentMessage never rejects: on any error it emits an 'error' event on the Subscription + sub.handleResentMessage( + msg.streamMessage, msg.requestId, + once(() => stream.verifyStreamMessage(msg.streamMessage)), // ensure verification occurs only once + ) + } else { + this.debug('WARN: request id not found for stream: %s, sub: %s', msg.streamMessage.getStreamId(), msg.requestId) + } + } else { + this.debug('WARN: message received for stream with no subscriptions: %s', msg.streamMessage.getStreamId()) + } + }) + + // Route resending state messages to corresponding Subscriptions + this.client.connection.on(ControlMessage.TYPES.ResendResponseResending, (response) => { + const stream = this.client._getSubscribedStreamPartition(response.streamId, response.streamPartition) + const sub = this.resendUtil.getSubFromResendResponse(response) + + if (stream && sub && stream.getSubscription(sub.id)) { + stream.getSubscription(sub.id).handleResending(response) + } else { + this.debug('resent: Subscription %s is gone already', response.requestId) + } + }) + + this.client.connection.on(ControlMessage.TYPES.ResendResponseNoResend, (response) => { + const stream = this.client._getSubscribedStreamPartition(response.streamId, response.streamPartition) + const sub = this.resendUtil.getSubFromResendResponse(response) + this.resendUtil.deleteDoneSubsByResponse(response) + + if (stream && sub && stream.getSubscription(sub.id)) { + stream.getSubscription(sub.id).handleNoResend(response) + } else { + this.debug('resent: Subscription %s is gone already', response.requestId) + } + }) + + this.client.connection.on(ControlMessage.TYPES.ResendResponseResent, (response) => { + const stream = this.client._getSubscribedStreamPartition(response.streamId, response.streamPartition) + const sub = this.resendUtil.getSubFromResendResponse(response) + this.resendUtil.deleteDoneSubsByResponse(response) + + if (stream && sub && stream.getSubscription(sub.id)) { + stream.getSubscription(sub.id).handleResent(response) + } else { + this.debug('resent: Subscription %s is gone already', response.requestId) + } + }) + } + + async resend(optionsOrStreamId, callback) { + const options = this.client._validateParameters(optionsOrStreamId, callback) + + if (!options.stream) { + throw new Error('resend: Invalid arguments: options.stream is not given') + } + + if (!options.resend) { + throw new Error('resend: Invalid arguments: options.resend is not given') + } + + await this.client.ensureConnected() + const sub = new HistoricalSubscription({ + streamId: options.stream, + streamPartition: options.partition || 0, + callback, + options: options.resend, + propagationTimeout: this.client.options.gapFillTimeout, + resendTimeout: this.client.options.retryResendAfter, + orderMessages: this.client.orderMessages, + debug: this.debug, + }) + + // TODO remove _addSubscription after uncoupling Subscription and Resend + sub.setState(Subscription.State.subscribed) + this.client._addSubscription(sub) + sub.once('initial_resend_done', () => this.client._removeSubscription(sub)) + await this._requestResend(sub) + return sub + } + + async _requestResend(sub, resendOptions) { + sub.setResending(true) + const requestId = this.resendUtil.registerResendRequestForSub(sub) + const options = resendOptions || sub.getResendOptions() + const sessionToken = await this.client.session.getSessionToken() + // don't bother requesting resend if not connected + if (!this.client.isConnected()) { return } + let request + if (options.last > 0) { + request = new ResendLastRequest({ + streamId: sub.streamId, + streamPartition: sub.streamPartition, + requestId, + numberLast: options.last, + sessionToken, + }) + } else if (options.from && !options.to) { + request = new ResendFromRequest({ + streamId: sub.streamId, + streamPartition: sub.streamPartition, + requestId, + fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), + publisherId: options.publisherId, + msgChainId: options.msgChainId, + sessionToken, + }) + } else if (options.from && options.to) { + request = new ResendRangeRequest({ + streamId: sub.streamId, + streamPartition: sub.streamPartition, + requestId, + fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), + toMsgRef: new MessageRef(options.to.timestamp, options.to.sequenceNumber), + publisherId: options.publisherId, + msgChainId: options.msgChainId, + sessionToken, + }) + } + + if (request) { + this.debug('_requestResend: %o', request) + await this.client.connection.send(request).catch((err) => { + this.client.handleError(`Failed to send resend request: ${err}`) + }) + } else { + this.client.handleError("Can't _requestResend without resendOptions") + } + } +} diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 7f7ecb5d4..e421c19bb 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -6,7 +6,6 @@ import { Wallet } from 'ethers' import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' import uniqueId from 'lodash.uniqueid' -import HistoricalSubscription from './HistoricalSubscription' import Connection from './Connection' import Session from './Session' import SubscribedStreamPartition from './SubscribedStreamPartition' @@ -14,19 +13,12 @@ import { waitFor, getVersionString } from './utils' import RealTimeSubscription from './RealTimeSubscription' import CombinedSubscription from './CombinedSubscription' import Subscription from './Subscription' -import ResendUtil from './ResendUtil' import Publisher from './Publisher' +import Resender from './Resender' -const { - SubscribeRequest, - UnsubscribeRequest, - ResendLastRequest, - ResendFromRequest, - ResendRangeRequest, - ControlMessage, -} = ControlLayer +const { SubscribeRequest, UnsubscribeRequest, ControlMessage } = ControlLayer -const { StreamMessage, MessageRef } = MessageLayer +const { StreamMessage } = MessageLayer export default class StreamrClient extends EventEmitter { constructor(options, connection) { @@ -97,9 +89,6 @@ export default class StreamrClient extends EventEmitter { this.getUserInfo = this.getUserInfo.bind(this) - this.resendUtil = new ResendUtil() - this.resendUtil.on('error', (err) => this.emit('error', err)) - this.on('error', (...args) => { this.onError(...args) this.ensureDisconnected() @@ -117,26 +106,6 @@ export default class StreamrClient extends EventEmitter { } }) - // Unicast messages to a specific subscription only - this.connection.on(ControlMessage.TYPES.UnicastMessage, async (msg) => { - const stream = this._getSubscribedStreamPartition(msg.streamMessage.getStreamId(), msg.streamMessage.getStreamPartition()) - if (stream) { - const sub = this.resendUtil.getSubFromResendResponse(msg) - - if (sub && stream.getSubscription(sub.id)) { - // sub.handleResentMessage never rejects: on any error it emits an 'error' event on the Subscription - sub.handleResentMessage( - msg.streamMessage, msg.requestId, - once(() => stream.verifyStreamMessage(msg.streamMessage)), // ensure verification occurs only once - ) - } else { - this.debug('WARN: request id not found for stream: %s, sub: %s', msg.streamMessage.getStreamId(), msg.requestId) - } - } else { - this.debug('WARN: message received for stream with no subscriptions: %s', msg.streamMessage.getStreamId()) - } - }) - this.connection.on(ControlMessage.TYPES.SubscribeResponse, (response) => { const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) if (stream) { @@ -160,42 +129,6 @@ export default class StreamrClient extends EventEmitter { this._checkAutoDisconnect() }) - // Route resending state messages to corresponding Subscriptions - this.connection.on(ControlMessage.TYPES.ResendResponseResending, (response) => { - const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) - const sub = this.resendUtil.getSubFromResendResponse(response) - - if (stream && sub && stream.getSubscription(sub.id)) { - stream.getSubscription(sub.id).handleResending(response) - } else { - this.debug('resent: Subscription %s is gone already', response.requestId) - } - }) - - this.connection.on(ControlMessage.TYPES.ResendResponseNoResend, (response) => { - const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) - const sub = this.resendUtil.getSubFromResendResponse(response) - this.resendUtil.deleteDoneSubsByResponse(response) - - if (stream && sub && stream.getSubscription(sub.id)) { - stream.getSubscription(sub.id).handleNoResend(response) - } else { - this.debug('resent: Subscription %s is gone already', response.requestId) - } - }) - - this.connection.on(ControlMessage.TYPES.ResendResponseResent, (response) => { - const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) - const sub = this.resendUtil.getSubFromResendResponse(response) - this.resendUtil.deleteDoneSubsByResponse(response) - - if (stream && sub && stream.getSubscription(sub.id)) { - stream.getSubscription(sub.id).handleResent(response) - } else { - this.debug('resent: Subscription %s is gone already', response.requestId) - } - }) - // On connect/reconnect, send pending subscription requests this.connection.on('connected', async () => { await new Promise((resolve) => setTimeout(resolve, 0)) // wait a tick to let event handlers finish @@ -255,6 +188,7 @@ export default class StreamrClient extends EventEmitter { }) this.publisher = new Publisher(this) + this.resender = new Resender(this) } /** @@ -321,35 +255,8 @@ export default class StreamrClient extends EventEmitter { return subs } - async resend(optionsOrStreamId, callback) { - const options = this._validateParameters(optionsOrStreamId, callback) - - if (!options.stream) { - throw new Error('resend: Invalid arguments: options.stream is not given') - } - - if (!options.resend) { - throw new Error('resend: Invalid arguments: options.resend is not given') - } - - await this.ensureConnected() - const sub = new HistoricalSubscription({ - streamId: options.stream, - streamPartition: options.partition || 0, - callback, - options: options.resend, - propagationTimeout: this.options.gapFillTimeout, - resendTimeout: this.options.retryResendAfter, - orderMessages: this.orderMessages, - debug: this.debug, - }) - - // TODO remove _addSubscription after uncoupling Subscription and Resend - sub.setState(Subscription.State.subscribed) - this._addSubscription(sub) - sub.once('initial_resend_done', () => this._removeSubscription(sub)) - await this._requestResend(sub) - return sub + async resend(...args) { + return this.resender.resend(...args) } // eslint-disable-next-line class-methods-use-this @@ -415,7 +322,7 @@ export default class StreamrClient extends EventEmitter { } sub.on('gap', (from, to, publisherId, msgChainId) => { if (!sub.resending) { - this._requestResend(sub, { + this.resender._requestResend(sub, { from, to, publisherId, msgChainId, }) } @@ -594,19 +501,7 @@ export default class StreamrClient extends EventEmitter { sub.once('subscribed', () => { if (!sub.hasResendOptions()) { return } - this._requestResend(sub) - // once a message is received, gap filling in Subscription.js will check if this satisfies the resend and request - // another resend if it doesn't. So we can anyway clear this resend request. - const handler = () => { - sub.removeListener('initial_resend_done', handler) - sub.removeListener('message received', handler) - sub.removeListener('unsubscribed', handler) - sub.removeListener('error', handler) - } - sub.once('initial_resend_done', handler) - sub.once('message received', handler) - sub.once('unsubscribed', handler) - sub.once('error', handler) + this.resender._requestResend(sub) }) await this._requestSubscribe(sub) } @@ -632,7 +527,7 @@ export default class StreamrClient extends EventEmitter { streamId: sub.streamId, streamPartition: sub.streamPartition, sessionToken, - requestId: this.resendUtil.generateRequestId(), + requestId: this.resender.resendUtil.generateRequestId(), }) this.debug('_requestSubscribe: subscribing client: %o', request) sp.setSubscribing(true) @@ -655,7 +550,7 @@ export default class StreamrClient extends EventEmitter { const unsubscribeRequest = new UnsubscribeRequest({ streamId: sub.streamId, streamPartition: sub.streamPartition, - requestId: this.resendUtil.generateRequestId(), + requestId: this.resender.resendUtil.generateRequestId(), }) await this.connection.send(unsubscribeRequest).catch((err) => { sub.setState(Subscription.State.subscribed) @@ -663,54 +558,6 @@ export default class StreamrClient extends EventEmitter { }) } - async _requestResend(sub, resendOptions) { - sub.setResending(true) - const requestId = this.resendUtil.registerResendRequestForSub(sub) - const options = resendOptions || sub.getResendOptions() - const sessionToken = await this.session.getSessionToken() - // don't bother requesting resend if not connected - if (!this.isConnected()) { return } - let request - if (options.last > 0) { - request = new ResendLastRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId, - numberLast: options.last, - sessionToken, - }) - } else if (options.from && !options.to) { - request = new ResendFromRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId, - fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), - publisherId: options.publisherId, - msgChainId: options.msgChainId, - sessionToken, - }) - } else if (options.from && options.to) { - request = new ResendRangeRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId, - fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), - toMsgRef: new MessageRef(options.to.timestamp, options.to.sequenceNumber), - publisherId: options.publisherId, - msgChainId: options.msgChainId, - sessionToken, - }) - } - - if (request) { - this.debug('_requestResend: %o', request) - await this.connection.send(request).catch((err) => { - this.handleError(`Failed to send resend request: ${err}`) - }) - } else { - this.handleError("Can't _requestResend without resendOptions") - } - } handleError(msg) { this.debug(msg) From c2a23af55ca5d947411cd1e0749ff70f80dc9126 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 12 Aug 2020 12:35:31 -0400 Subject: [PATCH 010/517] Refactor Subscribe logic into src/Subscriber. --- src/Publisher.js | 2 +- src/Resender.js | 14 +- src/StreamrClient.js | 359 ++----------------------- src/Subscriber.js | 359 +++++++++++++++++++++++++ test/integration/StreamrClient.test.js | 4 +- test/unit/StreamrClient.test.js | 2 +- 6 files changed, 392 insertions(+), 348 deletions(-) create mode 100644 src/Subscriber.js diff --git a/src/Publisher.js b/src/Publisher.js index 8cb5d49a6..941007d04 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -41,7 +41,7 @@ export default class Publisher { // On connect/reconnect, send pending subscription requests this.onConnected = this.onConnected.bind(this) - client.connection.on('connected', this.onConnected) + client.on('connected', this.onConnected) } async onConnected() { diff --git a/src/Resender.js b/src/Resender.js index 80656a41f..ee0f3224d 100644 --- a/src/Resender.js +++ b/src/Resender.js @@ -18,7 +18,7 @@ export default class Resender { // Unicast messages to a specific subscription only this.client.connection.on(ControlMessage.TYPES.UnicastMessage, async (msg) => { - const stream = this.client._getSubscribedStreamPartition(msg.streamMessage.getStreamId(), msg.streamMessage.getStreamPartition()) + const stream = this.client.subscriber._getSubscribedStreamPartition(msg.streamMessage.getStreamId(), msg.streamMessage.getStreamPartition()) if (stream) { const sub = this.resendUtil.getSubFromResendResponse(msg) @@ -38,7 +38,7 @@ export default class Resender { // Route resending state messages to corresponding Subscriptions this.client.connection.on(ControlMessage.TYPES.ResendResponseResending, (response) => { - const stream = this.client._getSubscribedStreamPartition(response.streamId, response.streamPartition) + const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) const sub = this.resendUtil.getSubFromResendResponse(response) if (stream && sub && stream.getSubscription(sub.id)) { @@ -49,7 +49,7 @@ export default class Resender { }) this.client.connection.on(ControlMessage.TYPES.ResendResponseNoResend, (response) => { - const stream = this.client._getSubscribedStreamPartition(response.streamId, response.streamPartition) + const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) const sub = this.resendUtil.getSubFromResendResponse(response) this.resendUtil.deleteDoneSubsByResponse(response) @@ -61,7 +61,7 @@ export default class Resender { }) this.client.connection.on(ControlMessage.TYPES.ResendResponseResent, (response) => { - const stream = this.client._getSubscribedStreamPartition(response.streamId, response.streamPartition) + const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) const sub = this.resendUtil.getSubFromResendResponse(response) this.resendUtil.deleteDoneSubsByResponse(response) @@ -74,7 +74,7 @@ export default class Resender { } async resend(optionsOrStreamId, callback) { - const options = this.client._validateParameters(optionsOrStreamId, callback) + const options = this.client.subscriber._validateParameters(optionsOrStreamId, callback) if (!options.stream) { throw new Error('resend: Invalid arguments: options.stream is not given') @@ -98,8 +98,8 @@ export default class Resender { // TODO remove _addSubscription after uncoupling Subscription and Resend sub.setState(Subscription.State.subscribed) - this.client._addSubscription(sub) - sub.once('initial_resend_done', () => this.client._removeSubscription(sub)) + this.client.subscriber._addSubscription(sub) + sub.once('initial_resend_done', () => this.client.subscriber._removeSubscription(sub)) await this._requestResend(sub) return sub } diff --git a/src/StreamrClient.js b/src/StreamrClient.js index e421c19bb..23dbd1fe8 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -1,22 +1,18 @@ import EventEmitter from 'eventemitter3' import debugFactory from 'debug' import qs from 'qs' -import once from 'once' import { Wallet } from 'ethers' import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' import uniqueId from 'lodash.uniqueid' import Connection from './Connection' import Session from './Session' -import SubscribedStreamPartition from './SubscribedStreamPartition' import { waitFor, getVersionString } from './utils' -import RealTimeSubscription from './RealTimeSubscription' -import CombinedSubscription from './CombinedSubscription' -import Subscription from './Subscription' import Publisher from './Publisher' import Resender from './Resender' +import Subscriber from './Subscriber' -const { SubscribeRequest, UnsubscribeRequest, ControlMessage } = ControlLayer +const { ControlMessage } = ControlLayer const { StreamMessage } = MessageLayer @@ -47,8 +43,6 @@ export default class StreamrClient extends EventEmitter { tokenAddress: '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', } - this.subscribedStreamPartitions = {} - Object.assign(this.options, options || {}) const parts = this.options.url.split('?') @@ -94,76 +88,17 @@ export default class StreamrClient extends EventEmitter { this.ensureDisconnected() }) - // Broadcast messages to all subs listening on stream-partition - this.connection.on(ControlMessage.TYPES.BroadcastMessage, (msg) => { - const stream = this._getSubscribedStreamPartition(msg.streamMessage.getStreamId(), msg.streamMessage.getStreamPartition()) - if (stream) { - const verifyFn = once(() => stream.verifyStreamMessage(msg.streamMessage)) // ensure verification occurs only once - // sub.handleBroadcastMessage never rejects: on any error it emits an 'error' event on the Subscription - stream.getSubscriptions().forEach((sub) => sub.handleBroadcastMessage(msg.streamMessage, verifyFn)) - } else { - this.debug('WARN: message received for stream with no subscriptions: %s', msg.streamMessage.getStreamId()) - } - }) - - this.connection.on(ControlMessage.TYPES.SubscribeResponse, (response) => { - const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) - if (stream) { - stream.setSubscribing(false) - stream.getSubscriptions().filter((sub) => !sub.resending) - .forEach((sub) => sub.setState(Subscription.State.subscribed)) - } - this.debug('Client subscribed: streamId: %s, streamPartition: %s', response.streamId, response.streamPartition) - }) - - this.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (response) => { - this.debug('Client unsubscribed: streamId: %s, streamPartition: %s', response.streamId, response.streamPartition) - const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) - if (stream) { - stream.getSubscriptions().forEach((sub) => { - this._removeSubscription(sub) - sub.setState(Subscription.State.unsubscribed) - }) - } - - this._checkAutoDisconnect() - }) - // On connect/reconnect, send pending subscription requests this.connection.on('connected', async () => { await new Promise((resolve) => setTimeout(resolve, 0)) // wait a tick to let event handlers finish if (!this.isConnected()) { return } this.debug('Connected!') this.emit('connected') - try { - if (!this.isConnected()) { return } - // Check pending subscriptions - Object.keys(this.subscribedStreamPartitions).forEach((key) => { - this.subscribedStreamPartitions[key].getSubscriptions().forEach((sub) => { - if (sub.getState() !== Subscription.State.subscribed) { - this._resendAndSubscribe(sub).catch((err) => { - this.emit('error', err) - }) - } - }) - }) - } catch (err) { - this.emit('error', err) - } }) this.connection.on('disconnected', () => { this.debug('Disconnected.') this.emit('disconnected') - - Object.keys(this.subscribedStreamPartitions) - .forEach((key) => { - const stream = this.subscribedStreamPartitions[key] - stream.setSubscribing(false) - stream.getSubscriptions().forEach((sub) => { - sub.onDisconnected() - }) - }) }) this.connection.on(ControlMessage.TYPES.ErrorResponse, (err) => { @@ -173,13 +108,8 @@ export default class StreamrClient extends EventEmitter { this.connection.on('error', async (err) => { // If there is an error parsing a json message in a stream, fire error events on the relevant subs - if (err instanceof Errors.InvalidJsonError) { - const stream = this._getSubscribedStreamPartition(err.streamMessage.getStreamId(), err.streamMessage.getStreamPartition()) - if (stream) { - stream.getSubscriptions().forEach((sub) => sub.handleError(err)) - } else { - this.debug('WARN: InvalidJsonError received for stream with no subscriptions: %s', err.streamId) - } + if ((err instanceof Errors.InvalidJsonError)) { + this.subscriber.onErrorMessage(err) } else { // if it looks like an error emit as-is, otherwise wrap in new Error const errorObject = (err && err.stack && err.message) ? err : new Error(err) @@ -188,6 +118,7 @@ export default class StreamrClient extends EventEmitter { }) this.publisher = new Publisher(this) + this.subscriber = new Subscriber(this) this.resender = new Resender(this) } @@ -199,199 +130,10 @@ export default class StreamrClient extends EventEmitter { console.error(error) } - _getSubscribedStreamPartition(streamId, streamPartition) { - const key = streamId + streamPartition - return this.subscribedStreamPartitions[key] - } - - _getSubscribedStreamPartitionsForStream(streamId) { - // TODO: pretty crude method, could improve - return Object.values(this.subscribedStreamPartitions) - .filter((stream) => stream.streamId === streamId) - } - - _addSubscribedStreamPartition(subscribedStreamPartition) { - const key = subscribedStreamPartition.streamId + subscribedStreamPartition.streamPartition - this.subscribedStreamPartitions[key] = subscribedStreamPartition - } - - _deleteSubscribedStreamPartition(subscribedStreamPartition) { - const key = subscribedStreamPartition.streamId + subscribedStreamPartition.streamPartition - delete this.subscribedStreamPartitions[key] - } - - _addSubscription(sub) { - let sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) - if (!sp) { - sp = new SubscribedStreamPartition(this, sub.streamId, sub.streamPartition) - this._addSubscribedStreamPartition(sp) - } - sp.addSubscription(sub) - } - - _removeSubscription(sub) { - const sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) - if (sp) { - sp.removeSubscription(sub) - if (sp.getSubscriptions().length === 0) { - this._deleteSubscribedStreamPartition(sp) - } - } - } - - getSubscriptions(streamId, streamPartition) { - let subs = [] - - if (streamPartition) { - const sp = this._getSubscribedStreamPartition(streamId, streamPartition) - if (sp) { - subs = sp.getSubscriptions() - } - } else { - const sps = this._getSubscribedStreamPartitionsForStream(streamId) - sps.forEach((sp) => sp.getSubscriptions().forEach((sub) => subs.push(sub))) - } - - return subs - } - async resend(...args) { return this.resender.resend(...args) } - // eslint-disable-next-line class-methods-use-this - _validateParameters(optionsOrStreamId, callback) { - if (!optionsOrStreamId) { - throw new Error('subscribe/resend: Invalid arguments: options is required!') - } else if (!callback) { - throw new Error('subscribe/resend: Invalid arguments: callback is required!') - } - - // Backwards compatibility for giving a streamId as first argument - let options - if (typeof optionsOrStreamId === 'string') { - options = { - stream: optionsOrStreamId, - } - } else if (typeof optionsOrStreamId === 'object') { - // shallow copy - options = { - ...optionsOrStreamId - } - } else { - throw new Error(`subscribe/resend: options must be an object! Given: ${optionsOrStreamId}`) - } - - return options - } - - subscribe(optionsOrStreamId, callback, legacyOptions) { - const options = this._validateParameters(optionsOrStreamId, callback) - - // Backwards compatibility for giving an options object as third argument - Object.assign(options, legacyOptions) - - if (!options.stream) { - throw new Error('subscribe: Invalid arguments: options.stream is not given') - } - - // Create the Subscription object and bind handlers - let sub - if (options.resend) { - sub = new CombinedSubscription({ - streamId: options.stream, - streamPartition: options.partition || 0, - callback, - options: options.resend, - propagationTimeout: this.options.gapFillTimeout, - resendTimeout: this.options.retryResendAfter, - orderMessages: this.options.orderMessages, - debug: this.debug, - }) - } else { - sub = new RealTimeSubscription({ - streamId: options.stream, - streamPartition: options.partition || 0, - callback, - options: options.resend, - propagationTimeout: this.options.gapFillTimeout, - resendTimeout: this.options.retryResendAfter, - orderMessages: this.options.orderMessages, - debug: this.debug, - }) - } - sub.on('gap', (from, to, publisherId, msgChainId) => { - if (!sub.resending) { - this.resender._requestResend(sub, { - from, to, publisherId, msgChainId, - }) - } - }) - sub.on('done', () => { - this.debug('done event for sub %d', sub.id) - this.unsubscribe(sub) - }) - - // Add to lookups - this._addSubscription(sub) - - // If connected, emit a subscribe request - if (this.isConnected()) { - this._resendAndSubscribe(sub) - } else if (this.options.autoConnect) { - this.ensureConnected() - } - - return sub - } - - unsubscribe(sub) { - if (!sub || !sub.streamId) { - throw new Error('unsubscribe: please give a Subscription object as an argument!') - } - - const sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) - - // If this is the last subscription for this stream-partition, unsubscribe the client too - if (sp && sp.getSubscriptions().length === 1 - && this.isConnected() - && sub.getState() === Subscription.State.subscribed) { - sub.setState(Subscription.State.unsubscribing) - this._requestUnsubscribe(sub) - } else if (sub.getState() !== Subscription.State.unsubscribing && sub.getState() !== Subscription.State.unsubscribed) { - // Else the sub can be cleaned off immediately - this._removeSubscription(sub) - sub.setState(Subscription.State.unsubscribed) - this._checkAutoDisconnect() - } - } - - unsubscribeAll(streamId, streamPartition) { - if (!streamId) { - throw new Error('unsubscribeAll: a stream id is required!') - } else if (typeof streamId !== 'string') { - throw new Error('unsubscribe: stream id must be a string!') - } - - let streamPartitions = [] - - // Unsubscribe all subs for the given stream-partition - if (streamPartition) { - const sp = this._getSubscribedStreamPartition(streamId, streamPartition) - if (sp) { - streamPartitions = [sp] - } - } else { - streamPartitions = this._getSubscribedStreamPartitionsForStream(streamId) - } - - streamPartitions.forEach((sp) => { - sp.getSubscriptions().forEach((sub) => { - this.unsubscribe(sub) - }) - }) - } - isConnected() { return this.connection.state === Connection.State.CONNECTED } @@ -436,7 +178,7 @@ export default class StreamrClient extends EventEmitter { disconnect() { this.publisher.stop() - this.subscribedStreamPartitions = {} + this.subscriber.stop() return this.connection.disconnect() } @@ -452,6 +194,22 @@ export default class StreamrClient extends EventEmitter { return this.publisher.getPublisherId() } + subscribe(...args) { + return this.subscriber.subscribe(...args) + } + + unsubscribe(...args) { + return this.subscriber.unsubscribe(...args) + } + + unsubscribeAll(...args) { + return this.subscriber.unsubscribeAll(...args) + } + + getSubscriptions(...args) { + return this.subscriber.getSubscriptions(...args) + } + /** * Starts new connection if disconnected. * Waits for connection if connecting. @@ -486,79 +244,6 @@ export default class StreamrClient extends EventEmitter { await this.disconnect() } - _checkAutoDisconnect() { - // Disconnect if no longer subscribed to any streams - if (this.options.autoDisconnect && Object.keys(this.subscribedStreamPartitions).length === 0) { - this.debug('Disconnecting due to no longer being subscribed to any streams') - this.disconnect() - } - } - - async _resendAndSubscribe(sub) { - if (sub.getState() === Subscription.State.subscribing || sub.resending) { return } - sub.setState(Subscription.State.subscribing) - // Once subscribed, ask for a resend - sub.once('subscribed', () => { - if (!sub.hasResendOptions()) { return } - - this.resender._requestResend(sub) - }) - await this._requestSubscribe(sub) - } - - async _requestSubscribe(sub) { - const sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) - let subscribedSubs = [] - // never reuse subscriptions when incoming subscription needs resends - // i.e. only reuse realtime subscriptions - if (!sub.hasResendOptions()) { - subscribedSubs = sp.getSubscriptions().filter((it) => ( - it.getState() === Subscription.State.subscribed - // don't resuse subscriptions currently resending - && !it.isResending() - )) - } - - const sessionToken = await this.session.getSessionToken() - - // If this is the first subscription for this stream-partition, send a subscription request to the server - if (!sp.isSubscribing() && subscribedSubs.length === 0) { - const request = new SubscribeRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - sessionToken, - requestId: this.resender.resendUtil.generateRequestId(), - }) - this.debug('_requestSubscribe: subscribing client: %o', request) - sp.setSubscribing(true) - await this.connection.send(request).catch((err) => { - sub.setState(Subscription.State.unsubscribed) - this.emit('error', `Failed to send subscribe request: ${err}`) - }) - } else if (subscribedSubs.length > 0) { - // If there already is a subscribed subscription for this stream, this new one will just join it immediately - this.debug('_requestSubscribe: another subscription for same stream: %s, insta-subscribing', sub.streamId) - - setTimeout(() => { - sub.setState(Subscription.State.subscribed) - }) - } - } - - async _requestUnsubscribe(sub) { - this.debug('Client unsubscribing stream %o partition %o', sub.streamId, sub.streamPartition) - const unsubscribeRequest = new UnsubscribeRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId: this.resender.resendUtil.generateRequestId(), - }) - await this.connection.send(unsubscribeRequest).catch((err) => { - sub.setState(Subscription.State.subscribed) - this.handleError(`Failed to send unsubscribe request: ${err}`) - }) - } - - handleError(msg) { this.debug(msg) this.emit('error', msg) diff --git a/src/Subscriber.js b/src/Subscriber.js new file mode 100644 index 000000000..45e8e7468 --- /dev/null +++ b/src/Subscriber.js @@ -0,0 +1,359 @@ +import once from 'once' +import { ControlLayer, Errors } from 'streamr-client-protocol' + +import SubscribedStreamPartition from './SubscribedStreamPartition' +import RealTimeSubscription from './RealTimeSubscription' +import CombinedSubscription from './CombinedSubscription' +import Subscription from './Subscription' + +const { SubscribeRequest, UnsubscribeRequest, ControlMessage } = ControlLayer + +export default class Subscriber { + constructor(client) { + this.client = client + this.subscribedStreamPartitions = {} + this.debug = client.debug.extend('Subscriber') + + // Broadcast messages to all subs listening on stream-partition + this.client.connection.on(ControlMessage.TYPES.BroadcastMessage, (msg) => { + const stream = this._getSubscribedStreamPartition(msg.streamMessage.getStreamId(), msg.streamMessage.getStreamPartition()) + if (stream) { + const verifyFn = once(() => stream.verifyStreamMessage(msg.streamMessage)) // ensure verification occurs only once + // sub.handleBroadcastMessage never rejects: on any error it emits an 'error' event on the Subscription + stream.getSubscriptions().forEach((sub) => sub.handleBroadcastMessage(msg.streamMessage, verifyFn)) + } else { + this.debug('WARN: message received for stream with no subscriptions: %s', msg.streamMessage.getStreamId()) + } + }) + + this.client.connection.on(ControlMessage.TYPES.SubscribeResponse, (response) => { + const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) + if (stream) { + stream.setSubscribing(false) + stream.getSubscriptions().filter((sub) => !sub.resending) + .forEach((sub) => sub.setState(Subscription.State.subscribed)) + } + this.debug('Client subscribed: streamId: %s, streamPartition: %s', response.streamId, response.streamPartition) + }) + + this.client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (response) => { + this.debug('Client unsubscribed: streamId: %s, streamPartition: %s', response.streamId, response.streamPartition) + const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) + if (stream) { + stream.getSubscriptions().forEach((sub) => { + this._removeSubscription(sub) + sub.setState(Subscription.State.unsubscribed) + }) + } + + this._checkAutoDisconnect() + }) + + this.client.on('connected', async () => { + try { + if (!this.client.isConnected()) { return } + // Check pending subscriptions + Object.keys(this.subscribedStreamPartitions).forEach((key) => { + this.subscribedStreamPartitions[key].getSubscriptions().forEach((sub) => { + if (sub.getState() !== Subscription.State.subscribed) { + this._resendAndSubscribe(sub).catch((err) => { + this.client.emit('error', err) + }) + } + }) + }) + } catch (err) { + this.client.emit('error', err) + } + }) + + this.client.on('disconnected', () => { + Object.keys(this.subscribedStreamPartitions) + .forEach((key) => { + const stream = this.subscribedStreamPartitions[key] + stream.setSubscribing(false) + stream.getSubscriptions().forEach((sub) => { + sub.onDisconnected() + }) + }) + }) + } + + onErrorMessage(err) { + if (!(err instanceof Errors.InvalidJsonError)) { + return + } + // If there is an error parsing a json message in a stream, fire error events on the relevant subs + const stream = this._getSubscribedStreamPartition(err.streamMessage.getStreamId(), err.streamMessage.getStreamPartition()) + if (stream) { + stream.getSubscriptions().forEach((sub) => sub.handleError(err)) + } else { + this.debug('WARN: InvalidJsonError received for stream with no subscriptions: %s', err.streamId) + } + } + + subscribe(optionsOrStreamId, callback, legacyOptions) { + const options = this._validateParameters(optionsOrStreamId, callback) + + // Backwards compatibility for giving an options object as third argument + Object.assign(options, legacyOptions) + + if (!options.stream) { + throw new Error('subscribe: Invalid arguments: options.stream is not given') + } + + // Create the Subscription object and bind handlers + let sub + if (options.resend) { + sub = new CombinedSubscription({ + streamId: options.stream, + streamPartition: options.partition || 0, + callback, + options: options.resend, + propagationTimeout: this.client.options.gapFillTimeout, + resendTimeout: this.client.options.retryResendAfter, + orderMessages: this.client.options.orderMessages, + debug: this.debug, + }) + } else { + sub = new RealTimeSubscription({ + streamId: options.stream, + streamPartition: options.partition || 0, + callback, + options: options.resend, + propagationTimeout: this.client.options.gapFillTimeout, + resendTimeout: this.client.options.retryResendAfter, + orderMessages: this.client.options.orderMessages, + debug: this.debug, + }) + } + sub.on('gap', (from, to, publisherId, msgChainId) => { + if (!sub.resending) { + this.client.resender._requestResend(sub, { + from, to, publisherId, msgChainId, + }) + } + }) + sub.on('done', () => { + this.debug('done event for sub %d', sub.id) + this.unsubscribe(sub) + }) + + // Add to lookups + this._addSubscription(sub) + + // If connected, emit a subscribe request + if (this.client.isConnected()) { + this._resendAndSubscribe(sub) + } else if (this.client.options.autoConnect) { + this.client.ensureConnected() + } + + return sub + } + + unsubscribe(sub) { + if (!sub || !sub.streamId) { + throw new Error('unsubscribe: please give a Subscription object as an argument!') + } + + const sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) + + // If this is the last subscription for this stream-partition, unsubscribe the client too + if (sp && sp.getSubscriptions().length === 1 + && this.client.isConnected() + && sub.getState() === Subscription.State.subscribed) { + sub.setState(Subscription.State.unsubscribing) + this._requestUnsubscribe(sub) + } else if (sub.getState() !== Subscription.State.unsubscribing && sub.getState() !== Subscription.State.unsubscribed) { + // Else the sub can be cleaned off immediately + this._removeSubscription(sub) + sub.setState(Subscription.State.unsubscribed) + this._checkAutoDisconnect() + } + } + + unsubscribeAll(streamId, streamPartition) { + if (!streamId) { + throw new Error('unsubscribeAll: a stream id is required!') + } else if (typeof streamId !== 'string') { + throw new Error('unsubscribe: stream id must be a string!') + } + + let streamPartitions = [] + + // Unsubscribe all subs for the given stream-partition + if (streamPartition) { + const sp = this._getSubscribedStreamPartition(streamId, streamPartition) + if (sp) { + streamPartitions = [sp] + } + } else { + streamPartitions = this._getSubscribedStreamPartitionsForStream(streamId) + } + + streamPartitions.forEach((sp) => { + sp.getSubscriptions().forEach((sub) => { + this.unsubscribe(sub) + }) + }) + } + + _getSubscribedStreamPartition(streamId, streamPartition) { + const key = streamId + streamPartition + return this.subscribedStreamPartitions[key] + } + + _getSubscribedStreamPartitionsForStream(streamId) { + // TODO: pretty crude method, could improve + return Object.values(this.subscribedStreamPartitions) + .filter((stream) => stream.streamId === streamId) + } + + _addSubscribedStreamPartition(subscribedStreamPartition) { + const key = subscribedStreamPartition.streamId + subscribedStreamPartition.streamPartition + this.subscribedStreamPartitions[key] = subscribedStreamPartition + } + + _deleteSubscribedStreamPartition(subscribedStreamPartition) { + const key = subscribedStreamPartition.streamId + subscribedStreamPartition.streamPartition + delete this.subscribedStreamPartitions[key] + } + + _addSubscription(sub) { + let sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) + if (!sp) { + sp = new SubscribedStreamPartition(this.client, sub.streamId, sub.streamPartition) + this._addSubscribedStreamPartition(sp) + } + sp.addSubscription(sub) + } + + _removeSubscription(sub) { + const sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) + if (sp) { + sp.removeSubscription(sub) + if (sp.getSubscriptions().length === 0) { + this._deleteSubscribedStreamPartition(sp) + } + } + } + + getSubscriptions(streamId, streamPartition) { + let subs = [] + + if (streamPartition) { + const sp = this._getSubscribedStreamPartition(streamId, streamPartition) + if (sp) { + subs = sp.getSubscriptions() + } + } else { + const sps = this._getSubscribedStreamPartitionsForStream(streamId) + sps.forEach((sp) => sp.getSubscriptions().forEach((sub) => subs.push(sub))) + } + + return subs + } + + stop() { + this.subscribedStreamPartitions = {} + } + + async _requestSubscribe(sub) { + const sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) + let subscribedSubs = [] + // never reuse subscriptions when incoming subscription needs resends + // i.e. only reuse realtime subscriptions + if (!sub.hasResendOptions()) { + subscribedSubs = sp.getSubscriptions().filter((it) => ( + it.getState() === Subscription.State.subscribed + // don't resuse subscriptions currently resending + && !it.isResending() + )) + } + + const sessionToken = await this.client.session.getSessionToken() + + // If this is the first subscription for this stream-partition, send a subscription request to the server + if (!sp.isSubscribing() && subscribedSubs.length === 0) { + const request = new SubscribeRequest({ + streamId: sub.streamId, + streamPartition: sub.streamPartition, + sessionToken, + requestId: this.client.resender.resendUtil.generateRequestId(), + }) + this.debug('_requestSubscribe: subscribing client: %o', request) + sp.setSubscribing(true) + await this.client.connection.send(request).catch((err) => { + sub.setState(Subscription.State.unsubscribed) + this.client.emit('error', err) // `Failed to send subscribe request: ${err}`) + }) + } else if (subscribedSubs.length > 0) { + // If there already is a subscribed subscription for this stream, this new one will just join it immediately + this.debug('_requestSubscribe: another subscription for same stream: %s, insta-subscribing', sub.streamId) + + setTimeout(() => { + sub.setState(Subscription.State.subscribed) + }) + } + } + + async _requestUnsubscribe(sub) { + this.debug('Client unsubscribing stream %o partition %o', sub.streamId, sub.streamPartition) + const unsubscribeRequest = new UnsubscribeRequest({ + streamId: sub.streamId, + streamPartition: sub.streamPartition, + requestId: this.client.resender.resendUtil.generateRequestId(), + }) + await this.client.connection.send(unsubscribeRequest).catch((err) => { + sub.setState(Subscription.State.subscribed) + this.client.handleError(`Failed to send unsubscribe request: ${err}`) + }) + } + + _checkAutoDisconnect() { + // Disconnect if no longer subscribed to any streams + if (this.client.options.autoDisconnect && Object.keys(this.subscribedStreamPartitions).length === 0) { + this.debug('Disconnecting due to no longer being subscribed to any streams') + this.client.disconnect() + } + } + + // eslint-disable-next-line class-methods-use-this + _validateParameters(optionsOrStreamId, callback) { + if (!optionsOrStreamId) { + throw new Error('subscribe/resend: Invalid arguments: options is required!') + } else if (!callback) { + throw new Error('subscribe/resend: Invalid arguments: callback is required!') + } + + // Backwards compatibility for giving a streamId as first argument + let options + if (typeof optionsOrStreamId === 'string') { + options = { + stream: optionsOrStreamId, + } + } else if (typeof optionsOrStreamId === 'object') { + // shallow copy + options = { + ...optionsOrStreamId + } + } else { + throw new Error(`subscribe/resend: options must be an object! Given: ${optionsOrStreamId}`) + } + + return options + } + + async _resendAndSubscribe(sub) { + if (sub.getState() === Subscription.State.subscribing || sub.resending) { return } + sub.setState(Subscription.State.subscribing) + // Once subscribed, ask for a resend + sub.once('subscribed', () => { + if (!sub.hasResendOptions()) { return } + + this.client.resender._requestResend(sub) + }) + await this._requestSubscribe(sub) + } +} diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index 5424e5972..d975f4ade 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -779,7 +779,7 @@ describe('StreamrClient', () => { // Check signature stuff // WARNING: digging into internals - const subStream = client._getSubscribedStreamPartition(stream.id, 0) // eslint-disable-line no-underscore-dangle + const subStream = client.subscriber._getSubscribedStreamPartition(stream.id, 0) // eslint-disable-line no-underscore-dangle const publishers = await subStream.getPublishers() const map = {} map[client.publisher.signer.address.toLowerCase()] = true @@ -821,7 +821,7 @@ describe('StreamrClient', () => { // Check signature stuff // WARNING: digging into internals - const subStream = client._getSubscribedStreamPartition(stream.id, 0) // eslint-disable-line no-underscore-dangle + const subStream = client.subscriber._getSubscribedStreamPartition(stream.id, 0) // eslint-disable-line no-underscore-dangle const publishers = await subStream.getPublishers() const map = {} map[client.publisher.signer.address.toLowerCase()] = true diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index cbaf5ec19..67e9e0d0c 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -1233,7 +1233,7 @@ describe('StreamrClient', () => { it('unsubscribes', (done) => { const sub = mockSubscription('stream1', () => {}) - client.unsubscribe = (unsub) => { + client.subscriber.unsubscribe = (unsub) => { expect(sub).toBe(unsub) done() } From 15aefb5ba139ae6687d0039614030dc54e82a189 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 12 Aug 2020 13:13:04 -0400 Subject: [PATCH 011/517] Flatten some logic, disable eslint errors for _private access across Client sub-parts. --- src/Resender.js | 72 ++++++++++++++++++++++++++------------------ src/Subscriber.js | 76 +++++++++++++++++++++++++---------------------- 2 files changed, 84 insertions(+), 64 deletions(-) diff --git a/src/Resender.js b/src/Resender.js index ee0f3224d..1ebb19111 100644 --- a/src/Resender.js +++ b/src/Resender.js @@ -18,62 +18,73 @@ export default class Resender { // Unicast messages to a specific subscription only this.client.connection.on(ControlMessage.TYPES.UnicastMessage, async (msg) => { - const stream = this.client.subscriber._getSubscribedStreamPartition(msg.streamMessage.getStreamId(), msg.streamMessage.getStreamPartition()) - if (stream) { - const sub = this.resendUtil.getSubFromResendResponse(msg) - - if (sub && stream.getSubscription(sub.id)) { - // sub.handleResentMessage never rejects: on any error it emits an 'error' event on the Subscription - sub.handleResentMessage( - msg.streamMessage, msg.requestId, - once(() => stream.verifyStreamMessage(msg.streamMessage)), // ensure verification occurs only once - ) - } else { - this.debug('WARN: request id not found for stream: %s, sub: %s', msg.streamMessage.getStreamId(), msg.requestId) - } - } else { + // eslint-disable-next-line no-underscore-dangle + const stream = this.client.subscriber._getSubscribedStreamPartition( + msg.streamMessage.getStreamId(), + msg.streamMessage.getStreamPartition() + ) + + if (!stream) { this.debug('WARN: message received for stream with no subscriptions: %s', msg.streamMessage.getStreamId()) + return + } + + const sub = this.resendUtil.getSubFromResendResponse(msg) + + if (!sub || !stream.getSubscription(sub.id)) { + this.debug('WARN: request id not found for stream: %s, sub: %s', msg.streamMessage.getStreamId(), msg.requestId) + return } + // sub.handleResentMessage never rejects: on any error it emits an 'error' event on the Subscription + sub.handleResentMessage( + msg.streamMessage, msg.requestId, + once(() => stream.verifyStreamMessage(msg.streamMessage)), // ensure verification occurs only once + ) }) // Route resending state messages to corresponding Subscriptions this.client.connection.on(ControlMessage.TYPES.ResendResponseResending, (response) => { + // eslint-disable-next-line no-underscore-dangle const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) const sub = this.resendUtil.getSubFromResendResponse(response) - if (stream && sub && stream.getSubscription(sub.id)) { - stream.getSubscription(sub.id).handleResending(response) - } else { + if (!stream || !sub || !stream.getSubscription(sub.id)) { this.debug('resent: Subscription %s is gone already', response.requestId) + return } + stream.getSubscription(sub.id).handleResending(response) }) this.client.connection.on(ControlMessage.TYPES.ResendResponseNoResend, (response) => { + // eslint-disable-next-line no-underscore-dangle const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) const sub = this.resendUtil.getSubFromResendResponse(response) this.resendUtil.deleteDoneSubsByResponse(response) - if (stream && sub && stream.getSubscription(sub.id)) { - stream.getSubscription(sub.id).handleNoResend(response) - } else { + if (!stream || !sub || !stream.getSubscription(sub.id)) { this.debug('resent: Subscription %s is gone already', response.requestId) + return } + + stream.getSubscription(sub.id).handleNoResend(response) }) this.client.connection.on(ControlMessage.TYPES.ResendResponseResent, (response) => { + // eslint-disable-next-line no-underscore-dangle const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) const sub = this.resendUtil.getSubFromResendResponse(response) this.resendUtil.deleteDoneSubsByResponse(response) - if (stream && sub && stream.getSubscription(sub.id)) { - stream.getSubscription(sub.id).handleResent(response) - } else { + if (!stream || !sub || !stream.getSubscription(sub.id)) { this.debug('resent: Subscription %s is gone already', response.requestId) + return } + stream.getSubscription(sub.id).handleResent(response) }) } async resend(optionsOrStreamId, callback) { + // eslint-disable-next-line no-underscore-dangle const options = this.client.subscriber._validateParameters(optionsOrStreamId, callback) if (!options.stream) { @@ -98,7 +109,9 @@ export default class Resender { // TODO remove _addSubscription after uncoupling Subscription and Resend sub.setState(Subscription.State.subscribed) + // eslint-disable-next-line no-underscore-dangle this.client.subscriber._addSubscription(sub) + // eslint-disable-next-line no-underscore-dangle sub.once('initial_resend_done', () => this.client.subscriber._removeSubscription(sub)) await this._requestResend(sub) return sub @@ -143,13 +156,14 @@ export default class Resender { }) } - if (request) { - this.debug('_requestResend: %o', request) - await this.client.connection.send(request).catch((err) => { - this.client.handleError(`Failed to send resend request: ${err}`) - }) - } else { + if (!request) { this.client.handleError("Can't _requestResend without resendOptions") + return } + + this.debug('_requestResend: %o', request) + await this.client.connection.send(request).catch((err) => { + this.client.handleError(`Failed to send resend request: ${err}`) + }) } } diff --git a/src/Subscriber.js b/src/Subscriber.js index 45e8e7468..b538c0745 100644 --- a/src/Subscriber.js +++ b/src/Subscriber.js @@ -17,13 +17,13 @@ export default class Subscriber { // Broadcast messages to all subs listening on stream-partition this.client.connection.on(ControlMessage.TYPES.BroadcastMessage, (msg) => { const stream = this._getSubscribedStreamPartition(msg.streamMessage.getStreamId(), msg.streamMessage.getStreamPartition()) - if (stream) { - const verifyFn = once(() => stream.verifyStreamMessage(msg.streamMessage)) // ensure verification occurs only once - // sub.handleBroadcastMessage never rejects: on any error it emits an 'error' event on the Subscription - stream.getSubscriptions().forEach((sub) => sub.handleBroadcastMessage(msg.streamMessage, verifyFn)) - } else { + if (!stream) { this.debug('WARN: message received for stream with no subscriptions: %s', msg.streamMessage.getStreamId()) + return } + const verifyFn = once(() => stream.verifyStreamMessage(msg.streamMessage)) // ensure verification occurs only once + // sub.handleBroadcastMessage never rejects: on any error it emits an 'error' event on the Subscription + stream.getSubscriptions().forEach((sub) => sub.handleBroadcastMessage(msg.streamMessage, verifyFn)) }) this.client.connection.on(ControlMessage.TYPES.SubscribeResponse, (response) => { @@ -129,6 +129,7 @@ export default class Subscriber { } sub.on('gap', (from, to, publisherId, msgChainId) => { if (!sub.resending) { + // eslint-disable-next-line no-underscore-dangle this.client.resender._requestResend(sub, { from, to, publisherId, msgChainId, }) @@ -231,11 +232,12 @@ export default class Subscriber { _removeSubscription(sub) { const sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) - if (sp) { - sp.removeSubscription(sub) - if (sp.getSubscriptions().length === 0) { - this._deleteSubscribedStreamPartition(sp) - } + if (!sp) { + return + } + sp.removeSubscription(sub) + if (sp.getSubscriptions().length === 0) { + this._deleteSubscribedStreamPartition(sp) } } @@ -261,41 +263,45 @@ export default class Subscriber { async _requestSubscribe(sub) { const sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) - let subscribedSubs = [] // never reuse subscriptions when incoming subscription needs resends // i.e. only reuse realtime subscriptions if (!sub.hasResendOptions()) { - subscribedSubs = sp.getSubscriptions().filter((it) => ( + const subscribedSubs = sp.getSubscriptions().filter((it) => ( it.getState() === Subscription.State.subscribed // don't resuse subscriptions currently resending && !it.isResending() )) + + if (subscribedSubs.length) { + // If there already is a subscribed subscription for this stream, this new one will just join it immediately + this.debug('_requestSubscribe: another subscription for same stream: %s, insta-subscribing', sub.streamId) + + setTimeout(() => { + sub.setState(Subscription.State.subscribed) + }) + return + } } const sessionToken = await this.client.session.getSessionToken() - // If this is the first subscription for this stream-partition, send a subscription request to the server - if (!sp.isSubscribing() && subscribedSubs.length === 0) { - const request = new SubscribeRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - sessionToken, - requestId: this.client.resender.resendUtil.generateRequestId(), - }) - this.debug('_requestSubscribe: subscribing client: %o', request) - sp.setSubscribing(true) - await this.client.connection.send(request).catch((err) => { - sub.setState(Subscription.State.unsubscribed) - this.client.emit('error', err) // `Failed to send subscribe request: ${err}`) - }) - } else if (subscribedSubs.length > 0) { - // If there already is a subscribed subscription for this stream, this new one will just join it immediately - this.debug('_requestSubscribe: another subscription for same stream: %s, insta-subscribing', sub.streamId) - - setTimeout(() => { - sub.setState(Subscription.State.subscribed) - }) + if (sp.isSubscribing()) { + return } + + // If this is the first subscription for this stream-partition, send a subscription request to the server + const request = new SubscribeRequest({ + streamId: sub.streamId, + streamPartition: sub.streamPartition, + sessionToken, + requestId: this.client.resender.resendUtil.generateRequestId(), + }) + this.debug('_requestSubscribe: subscribing client: %o', request) + sp.setSubscribing(true) + await this.client.connection.send(request).catch((err) => { + sub.setState(Subscription.State.unsubscribed) + this.client.emit('error', `Failed to send subscribe request: ${err.stack}`) + }) } async _requestUnsubscribe(sub) { @@ -307,7 +313,7 @@ export default class Subscriber { }) await this.client.connection.send(unsubscribeRequest).catch((err) => { sub.setState(Subscription.State.subscribed) - this.client.handleError(`Failed to send unsubscribe request: ${err}`) + this.client.handleError(`Failed to send unsubscribe request: ${err.stack}`) }) } @@ -351,7 +357,7 @@ export default class Subscriber { // Once subscribed, ask for a resend sub.once('subscribed', () => { if (!sub.hasResendOptions()) { return } - + // eslint-disable-next-line no-underscore-dangle this.client.resender._requestResend(sub) }) await this._requestSubscribe(sub) From 85f035a3f693deb83a99de136e69029bf715e902 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 12 Aug 2020 13:41:25 -0400 Subject: [PATCH 012/517] Refactor event handlers into methods. --- src/Publisher.js | 4 +- src/Resender.js | 139 ++++++++++++++++++++++++------------------- src/StreamrClient.js | 85 +++++++++++++++----------- src/Subscriber.js | 129 ++++++++++++++++++++++----------------- 4 files changed, 203 insertions(+), 154 deletions(-) diff --git a/src/Publisher.js b/src/Publisher.js index 941007d04..13f28f32b 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -21,14 +21,14 @@ function getStreamId(streamObjectOrId) { export default class Publisher { constructor(client) { this.client = client + this.debug = client.debug.extend('Publisher') + this.publishQueue = [] this.signer = Signer.createSigner({ ...client.options.auth, debug: client.debug, }, client.options.publishWithSignature) - this.debug = client.debug.extend('Publisher') - if (client.session.isUnauthenticated()) { this.msgCreationUtil = null } else { diff --git a/src/Resender.js b/src/Resender.js index 1ebb19111..2f4a672f6 100644 --- a/src/Resender.js +++ b/src/Resender.js @@ -12,75 +12,94 @@ const { MessageRef } = MessageLayer export default class Resender { constructor(client) { this.client = client - this.resendUtil = new ResendUtil() - this.resendUtil.on('error', (err) => this.client.emit('error', err)) this.debug = client.debug.extend('Resends') + this.resendUtil = new ResendUtil() + + this.onResendUtilError = this.onResendUtilError.bind(this) + this.resendUtil.on('error', this.onResendUtilError) + // Unicast messages to a specific subscription only - this.client.connection.on(ControlMessage.TYPES.UnicastMessage, async (msg) => { - // eslint-disable-next-line no-underscore-dangle - const stream = this.client.subscriber._getSubscribedStreamPartition( - msg.streamMessage.getStreamId(), - msg.streamMessage.getStreamPartition() - ) - - if (!stream) { - this.debug('WARN: message received for stream with no subscriptions: %s', msg.streamMessage.getStreamId()) - return - } - - const sub = this.resendUtil.getSubFromResendResponse(msg) - - if (!sub || !stream.getSubscription(sub.id)) { - this.debug('WARN: request id not found for stream: %s, sub: %s', msg.streamMessage.getStreamId(), msg.requestId) - return - } - // sub.handleResentMessage never rejects: on any error it emits an 'error' event on the Subscription - sub.handleResentMessage( - msg.streamMessage, msg.requestId, - once(() => stream.verifyStreamMessage(msg.streamMessage)), // ensure verification occurs only once - ) - }) + this.onUnicastMessage = this.onUnicastMessage.bind(this) + this.client.connection.on(ControlMessage.TYPES.UnicastMessage, this.onUnicastMessage) // Route resending state messages to corresponding Subscriptions - this.client.connection.on(ControlMessage.TYPES.ResendResponseResending, (response) => { - // eslint-disable-next-line no-underscore-dangle - const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) - const sub = this.resendUtil.getSubFromResendResponse(response) - - if (!stream || !sub || !stream.getSubscription(sub.id)) { - this.debug('resent: Subscription %s is gone already', response.requestId) - return - } - stream.getSubscription(sub.id).handleResending(response) - }) + this.onResendResponseResending = this.onResendResponseResending.bind(this) + this.client.connection.on(ControlMessage.TYPES.ResendResponseResending, this.onResendResponseResending) - this.client.connection.on(ControlMessage.TYPES.ResendResponseNoResend, (response) => { - // eslint-disable-next-line no-underscore-dangle - const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) - const sub = this.resendUtil.getSubFromResendResponse(response) - this.resendUtil.deleteDoneSubsByResponse(response) + this.onResendResponseNoResend = this.onResendResponseNoResend.bind(this) + this.client.connection.on(ControlMessage.TYPES.ResendResponseNoResend, this.onResendResponseNoResend) - if (!stream || !sub || !stream.getSubscription(sub.id)) { - this.debug('resent: Subscription %s is gone already', response.requestId) - return - } + this.onResendResponseResent = this.onResendResponseResent.bind(this) + this.client.connection.on(ControlMessage.TYPES.ResendResponseResent, this.onResendResponseResent) + } - stream.getSubscription(sub.id).handleNoResend(response) - }) + onResendUtilError(err) { + this.client.emit('error', err) + } - this.client.connection.on(ControlMessage.TYPES.ResendResponseResent, (response) => { - // eslint-disable-next-line no-underscore-dangle - const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) - const sub = this.resendUtil.getSubFromResendResponse(response) - this.resendUtil.deleteDoneSubsByResponse(response) - - if (!stream || !sub || !stream.getSubscription(sub.id)) { - this.debug('resent: Subscription %s is gone already', response.requestId) - return - } - stream.getSubscription(sub.id).handleResent(response) - }) + onResendResponseResent(response) { + // eslint-disable-next-line no-underscore-dangle + const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) + const sub = this.resendUtil.getSubFromResendResponse(response) + this.resendUtil.deleteDoneSubsByResponse(response) + + if (!stream || !sub || !stream.getSubscription(sub.id)) { + this.debug('resent: Subscription %s is gone already', response.requestId) + return + } + stream.getSubscription(sub.id).handleResent(response) + } + + onResendResponseNoResend(response) { + // eslint-disable-next-line no-underscore-dangle + const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) + const sub = this.resendUtil.getSubFromResendResponse(response) + this.resendUtil.deleteDoneSubsByResponse(response) + + if (!stream || !sub || !stream.getSubscription(sub.id)) { + this.debug('resent: Subscription %s is gone already', response.requestId) + return + } + + stream.getSubscription(sub.id).handleNoResend(response) + } + + onResendResponseResending(response) { + // eslint-disable-next-line no-underscore-dangle + const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) + const sub = this.resendUtil.getSubFromResendResponse(response) + + if (!stream || !sub || !stream.getSubscription(sub.id)) { + this.debug('resent: Subscription %s is gone already', response.requestId) + return + } + stream.getSubscription(sub.id).handleResending(response) + } + + async onUnicastMessage(msg) { + // eslint-disable-next-line no-underscore-dangle + const stream = this.client.subscriber._getSubscribedStreamPartition( + msg.streamMessage.getStreamId(), + msg.streamMessage.getStreamPartition() + ) + + if (!stream) { + this.debug('WARN: message received for stream with no subscriptions: %s', msg.streamMessage.getStreamId()) + return + } + + const sub = this.resendUtil.getSubFromResendResponse(msg) + + if (!sub || !stream.getSubscription(sub.id)) { + this.debug('WARN: request id not found for stream: %s, sub: %s', msg.streamMessage.getStreamId(), msg.requestId) + return + } + // sub.handleResentMessage never rejects: on any error it emits an 'error' event on the Subscription + sub.handleResentMessage( + msg.streamMessage, msg.requestId, + once(() => stream.verifyStreamMessage(msg.streamMessage)), // ensure verification occurs only once + ) } async resend(optionsOrStreamId, callback) { diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 23dbd1fe8..4eecf5173 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -82,46 +82,59 @@ export default class StreamrClient extends EventEmitter { this.connection = connection || new Connection(this.options) this.getUserInfo = this.getUserInfo.bind(this) + this._onError = this._onError.bind(this) + this.onConnectionConnected = this.onConnectionConnected.bind(this) + this.onConnectionDisconnected = this.onConnectionDisconnected.bind(this) + this.onErrorResponse = this.onErrorResponse.bind(this) + this.onConnectionError = this.onConnectionError.bind(this) - this.on('error', (...args) => { - this.onError(...args) - this.ensureDisconnected() - }) // On connect/reconnect, send pending subscription requests - this.connection.on('connected', async () => { - await new Promise((resolve) => setTimeout(resolve, 0)) // wait a tick to let event handlers finish - if (!this.isConnected()) { return } - this.debug('Connected!') - this.emit('connected') - }) - - this.connection.on('disconnected', () => { - this.debug('Disconnected.') - this.emit('disconnected') - }) - - this.connection.on(ControlMessage.TYPES.ErrorResponse, (err) => { - const errorObject = new Error(err.errorMessage) - this.emit('error', errorObject) - }) - - this.connection.on('error', async (err) => { - // If there is an error parsing a json message in a stream, fire error events on the relevant subs - if ((err instanceof Errors.InvalidJsonError)) { - this.subscriber.onErrorMessage(err) - } else { - // if it looks like an error emit as-is, otherwise wrap in new Error - const errorObject = (err && err.stack && err.message) ? err : new Error(err) - this.emit('error', errorObject) - } - }) + this.connection.on('connected', this.onConnectionConnected) + this.connection.on('disconnected', this.onConnectionDisconnected) + + this.connection.on('error', this.onConnectionError) + this.connection.on(ControlMessage.TYPES.ErrorResponse, this.onErrorResponse) + this.on('error', this._onError) this.publisher = new Publisher(this) this.subscriber = new Subscriber(this) this.resender = new Resender(this) } + async onConnectionConnected() { + await new Promise((resolve) => setTimeout(resolve, 0)) // wait a tick to let event handlers finish + if (!this.isConnected()) { return } + this.debug('Connected!') + this.emit('connected') + } + + async onConnectionDisconnected() { + this.debug('Disconnected.') + this.emit('disconnected') + } + + onConnectionError(err) { + // If there is an error parsing a json message in a stream, fire error events on the relevant subs + if ((err instanceof Errors.InvalidJsonError)) { + this.subscriber.onErrorMessage(err) + } else { + // if it looks like an error emit as-is, otherwise wrap in new Error + const errorObject = (err && err.stack && err.message) ? err : new Error(err) + this.emit('error', errorObject) + } + } + + onErrorResponse(err) { + const errorObject = new Error(err.errorMessage) + this.emit('error', errorObject) + } + + _onError(...args) { + this.onError(...args) + this.ensureDisconnected() + } + /** * Override to control output */ @@ -130,6 +143,11 @@ export default class StreamrClient extends EventEmitter { console.error(error) } + handleError(msg) { + this.debug(msg) + this.emit('error', msg) + } + async resend(...args) { return this.resender.resend(...args) } @@ -244,11 +262,6 @@ export default class StreamrClient extends EventEmitter { await this.disconnect() } - handleError(msg) { - this.debug(msg) - this.emit('error', msg) - } - static generateEthereumAccount() { const wallet = Wallet.createRandom() return { diff --git a/src/Subscriber.js b/src/Subscriber.js index b538c0745..940fe3564 100644 --- a/src/Subscriber.js +++ b/src/Subscriber.js @@ -11,75 +11,92 @@ const { SubscribeRequest, UnsubscribeRequest, ControlMessage } = ControlLayer export default class Subscriber { constructor(client) { this.client = client - this.subscribedStreamPartitions = {} this.debug = client.debug.extend('Subscriber') + this.subscribedStreamPartitions = {} + + this.onBroadcastMessage = this.onBroadcastMessage.bind(this) + this.client.connection.on(ControlMessage.TYPES.BroadcastMessage, this.onBroadcastMessage) + + this.onSubscribeResponse = this.onSubscribeResponse.bind(this) + this.client.connection.on(ControlMessage.TYPES.SubscribeResponse, this.onSubscribeResponse) + + this.onUnsubscribeResponse = this.onUnsubscribeResponse.bind(this) + this.client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, this.onUnsubscribeResponse) + + this.onClientConnected = this.onClientConnected.bind(this) + this.client.on('connected', this.onClientConnected) + + this.onClientDisconnected = this.onClientDisconnected.bind(this) + this.client.on('disconnected', this.onClientDisconnected) + } + + onBroadcastMessage(msg) { // Broadcast messages to all subs listening on stream-partition - this.client.connection.on(ControlMessage.TYPES.BroadcastMessage, (msg) => { - const stream = this._getSubscribedStreamPartition(msg.streamMessage.getStreamId(), msg.streamMessage.getStreamPartition()) - if (!stream) { - this.debug('WARN: message received for stream with no subscriptions: %s', msg.streamMessage.getStreamId()) - return - } - const verifyFn = once(() => stream.verifyStreamMessage(msg.streamMessage)) // ensure verification occurs only once - // sub.handleBroadcastMessage never rejects: on any error it emits an 'error' event on the Subscription - stream.getSubscriptions().forEach((sub) => sub.handleBroadcastMessage(msg.streamMessage, verifyFn)) - }) + const stream = this._getSubscribedStreamPartition(msg.streamMessage.getStreamId(), msg.streamMessage.getStreamPartition()) + if (!stream) { + this.debug('WARN: message received for stream with no subscriptions: %s', msg.streamMessage.getStreamId()) + return + } - this.client.connection.on(ControlMessage.TYPES.SubscribeResponse, (response) => { - const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) - if (stream) { - stream.setSubscribing(false) - stream.getSubscriptions().filter((sub) => !sub.resending) - .forEach((sub) => sub.setState(Subscription.State.subscribed)) - } - this.debug('Client subscribed: streamId: %s, streamPartition: %s', response.streamId, response.streamPartition) - }) + const verifyFn = once(() => stream.verifyStreamMessage(msg.streamMessage)) // ensure verification occurs only once + // sub.handleBroadcastMessage never rejects: on any error it emits an 'error' event on the Subscription + stream.getSubscriptions().forEach((sub) => sub.handleBroadcastMessage(msg.streamMessage, verifyFn)) + } - this.client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (response) => { - this.debug('Client unsubscribed: streamId: %s, streamPartition: %s', response.streamId, response.streamPartition) - const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) - if (stream) { - stream.getSubscriptions().forEach((sub) => { - this._removeSubscription(sub) - sub.setState(Subscription.State.unsubscribed) - }) - } + onSubscribeResponse(response) { + const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) + if (stream) { + stream.setSubscribing(false) + stream.getSubscriptions().filter((sub) => !sub.resending) + .forEach((sub) => sub.setState(Subscription.State.subscribed)) + } + this.debug('Client subscribed: streamId: %s, streamPartition: %s', response.streamId, response.streamPartition) + } - this._checkAutoDisconnect() - }) + onUnsubscribeResponse(response) { + this.debug('Client unsubscribed: streamId: %s, streamPartition: %s', response.streamId, response.streamPartition) + const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) + if (stream) { + stream.getSubscriptions().forEach((sub) => { + this._removeSubscription(sub) + sub.setState(Subscription.State.unsubscribed) + }) + } - this.client.on('connected', async () => { - try { - if (!this.client.isConnected()) { return } - // Check pending subscriptions - Object.keys(this.subscribedStreamPartitions).forEach((key) => { - this.subscribedStreamPartitions[key].getSubscriptions().forEach((sub) => { - if (sub.getState() !== Subscription.State.subscribed) { - this._resendAndSubscribe(sub).catch((err) => { - this.client.emit('error', err) - }) - } - }) - }) - } catch (err) { - this.client.emit('error', err) - } - }) + this._checkAutoDisconnect() + } - this.client.on('disconnected', () => { - Object.keys(this.subscribedStreamPartitions) - .forEach((key) => { - const stream = this.subscribedStreamPartitions[key] - stream.setSubscribing(false) - stream.getSubscriptions().forEach((sub) => { - sub.onDisconnected() - }) + async onClientConnected() { + try { + if (!this.client.isConnected()) { return } + // Check pending subscriptions + Object.keys(this.subscribedStreamPartitions).forEach((key) => { + this.subscribedStreamPartitions[key].getSubscriptions().forEach((sub) => { + if (sub.getState() !== Subscription.State.subscribed) { + this._resendAndSubscribe(sub).catch((err) => { + this.client.emit('error', err) + }) + } }) + }) + } catch (err) { + this.client.emit('error', err) + } + } + + onClientDisconnected() { + Object.keys(this.subscribedStreamPartitions).forEach((key) => { + const stream = this.subscribedStreamPartitions[key] + stream.setSubscribing(false) + stream.getSubscriptions().forEach((sub) => { + sub.onDisconnected() + }) }) } onErrorMessage(err) { + // not ideal, see error handler in client if (!(err instanceof Errors.InvalidJsonError)) { return } From b99409d7851fff1cac2bf8d6ad9ae98e7d58750f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 12 Aug 2020 14:04:05 -0400 Subject: [PATCH 013/517] Mildly reorganise StreamrClient constructor. --- src/StreamrClient.js | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 4eecf5173..f37519557 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -77,29 +77,26 @@ export default class StreamrClient extends EventEmitter { this.options.auth.privateKey = `0x${this.options.auth.privateKey}` } - this.session = new Session(this, this.options.auth) - // Event handling on connection object - this.connection = connection || new Connection(this.options) - + // bind event handlers this.getUserInfo = this.getUserInfo.bind(this) - this._onError = this._onError.bind(this) this.onConnectionConnected = this.onConnectionConnected.bind(this) this.onConnectionDisconnected = this.onConnectionDisconnected.bind(this) + this._onError = this._onError.bind(this) this.onErrorResponse = this.onErrorResponse.bind(this) this.onConnectionError = this.onConnectionError.bind(this) + this.on('error', this._onError) // attach before creating sub-components incase they fire error events + + this.session = new Session(this, this.options.auth) + this.connection = connection || new Connection(this.options) + this.publisher = new Publisher(this) + this.subscriber = new Subscriber(this) + this.resender = new Resender(this) - // On connect/reconnect, send pending subscription requests this.connection.on('connected', this.onConnectionConnected) this.connection.on('disconnected', this.onConnectionDisconnected) - this.connection.on('error', this.onConnectionError) this.connection.on(ControlMessage.TYPES.ErrorResponse, this.onErrorResponse) - this.on('error', this._onError) - - this.publisher = new Publisher(this) - this.subscriber = new Subscriber(this) - this.resender = new Resender(this) } async onConnectionConnected() { From 0d8999042822dbbdf41ffb7ec7dacbf47989eabe Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 12 Aug 2020 14:08:05 -0400 Subject: [PATCH 014/517] Linting. --- src/CombinedSubscription.js | 1 - test/unit/StreamrClient.test.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/CombinedSubscription.js b/src/CombinedSubscription.js index 10885fb2f..8dbc426b5 100644 --- a/src/CombinedSubscription.js +++ b/src/CombinedSubscription.js @@ -1,7 +1,6 @@ import HistoricalSubscription from './HistoricalSubscription' import RealTimeSubscription from './RealTimeSubscription' import Subscription from './Subscription' -import AbstractSubscription from './AbstractSubscription' export default class CombinedSubscription extends Subscription { constructor({ diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index 67e9e0d0c..6d8c2df53 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -1508,7 +1508,7 @@ describe('StreamrClient', () => { c.once('connected', async () => { await wait() expect(requests[0]).toEqual(new SubscribeRequest({ - //streamId: getKeyExchangeStreamId('0x650EBB201f635652b44E4afD1e0193615922381D'), + // streamId: getKeyExchangeStreamId('0x650EBB201f635652b44E4afD1e0193615922381D'), streamPartition: 0, sessionToken, requestId: requests[0].requestId, From ec3a3bc66ef14574e114952dc27cafd3c6835d9e Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 14 Sep 2020 14:50:48 -0400 Subject: [PATCH 015/517] Prevent flakey dataunion server tests from failing build. --- .github/workflows/nodejs.yml | 4 ++-- package.json | 4 ++-- test/{integration => flakey}/DataUnionEndpoints.test.js | 0 3 files changed, 4 insertions(+), 4 deletions(-) rename test/{integration => flakey}/DataUnionEndpoints.test.js (100%) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 48e7ebbdc..19d165018 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -91,9 +91,9 @@ jobs: - name: test-integration run: npm run test-integration - - name: test-integration-flakey + - name: test-flakey continue-on-error: true - run: npm run test-integration-flakey + run: npm run test-flakey || echo "::warning::Flakey Tests Failed" streamr-client-testing-tool: needs: [test, lint] diff --git a/package.json b/package.json index e2ab9f3b3..6353bf685 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ "test": "jest --detectOpenHandles", "test-unit": "jest test/unit --detectOpenHandles", "coverage": "jest --coverage", - "test-integration": "jest --testPathIgnorePatterns='DataUnion' test/integration", - "test-integration-flakey": "jest test/integration/DataUnionEndpoints.test.js", + "test-integration": "jest test/integration", + "test-flakey": "jest test/flakey", "test-browser": "node ./test/browser/server.js & node node_modules/nightwatch/bin/nightwatch ./test/browser/browser.js && pkill -f server.js", "install-example": "cd examples/webpack && npm ci", "build-example": "cd examples/webpack && npm run build-with-parent" diff --git a/test/integration/DataUnionEndpoints.test.js b/test/flakey/DataUnionEndpoints.test.js similarity index 100% rename from test/integration/DataUnionEndpoints.test.js rename to test/flakey/DataUnionEndpoints.test.js From 2fcca5798446d442484bc2243d90940302cf12a1 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 14 Sep 2020 15:13:13 -0400 Subject: [PATCH 016/517] Fix up flakey test. --- test/flakey/DataUnionEndpoints.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/flakey/DataUnionEndpoints.test.js b/test/flakey/DataUnionEndpoints.test.js index db3fd1f67..7682eaa14 100644 --- a/test/flakey/DataUnionEndpoints.test.js +++ b/test/flakey/DataUnionEndpoints.test.js @@ -1,4 +1,3 @@ -/* eslint-disable no-await-in-loop */ import { Contract, providers, utils, Wallet } from 'ethers' import debug from 'debug' import { wait } from 'streamr-test-utils' @@ -7,11 +6,12 @@ import StreamrClient from '../../src' import * as Token from '../../contracts/TestToken.json' import { getEndpointUrl } from '../../src/utils' import authFetch from '../../src/rest/authFetch' - -import config from './config' +import config from '../integration/config' const log = debug('StreamrClient::DataUnionEndpoints::integration-test') +/* eslint-disable no-await-in-loop */ + describe('DataUnionEndPoints', () => { let dataUnion From f3dc2999748cc54a3c94ad1e3e22befd109e4c74 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 15 Sep 2020 10:24:02 -0400 Subject: [PATCH 017/517] Temporarily force-exit tests, this can be removed in next PR that cleans this up. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 6353bf685..767db4f1b 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,8 @@ "test": "jest --detectOpenHandles", "test-unit": "jest test/unit --detectOpenHandles", "coverage": "jest --coverage", - "test-integration": "jest test/integration", - "test-flakey": "jest test/flakey", + "test-integration": "jest --forceExit test/integration", + "test-flakey": "jest --forceExit test/flakey", "test-browser": "node ./test/browser/server.js & node node_modules/nightwatch/bin/nightwatch ./test/browser/browser.js && pkill -f server.js", "install-example": "cd examples/webpack && npm ci", "build-example": "cd examples/webpack && npm run build-with-parent" From 1c228361ebaf9334fc5b7fe89a259a07e2e8450b Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 19 Aug 2020 10:40:47 -0400 Subject: [PATCH 018/517] Disable max-classes-per-file eslint rule. --- .eslintrc.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 338d5fe40..7fa74bcad 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -19,7 +19,7 @@ module.exports = { } ], 'prefer-destructuring': 'warn', - + 'max-classes-per-file': 'off', // javascript is not java // TODO check all errors/warnings and create separate PR 'promise/always-return': 'warn', 'promise/catch-or-return': 'warn', From 0ecc77b68105329557c40e9bfefea7e001b638f2 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 19 Aug 2020 14:01:14 -0400 Subject: [PATCH 019/517] Add SocketConnection class. --- src/streams/SocketConnection.js | 201 +++++++++++++++++++++++++++ test/unit/streams/Connection.test.js | 174 +++++++++++++++++++++++ 2 files changed, 375 insertions(+) create mode 100644 src/streams/SocketConnection.js create mode 100644 test/unit/streams/Connection.test.js diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js new file mode 100644 index 000000000..cf1ce7c81 --- /dev/null +++ b/src/streams/SocketConnection.js @@ -0,0 +1,201 @@ +import EventEmitter from 'eventemitter3' +import debugFactory from 'debug' +import uniqueId from 'lodash.uniqueid' +import WebSocket from 'ws' + +/** + * Wraps WebSocket open/close with promise methods + * adds events + * reconnects on unexpected closure + * handles simultaneous calls to open/close + * waits for pending close/open before continuing + */ + +export default class SocketConnection extends EventEmitter { + constructor(options) { + super() + this.options = options + this.attempts = 0 + if (!options.url) { + throw new Error('URL is not defined!') + } + const id = uniqueId('SocketConnection') + if (options.debug) { + this.debug = options.debug.extend(id) + } else { + this.debug = debugFactory(`StreamrClient::${id}`) + } + } + + async open() { + if (this.isOpen()) { + this.openTask = undefined + return Promise.resolve() + } + + if (this.openTask) { + return this.openTask + } + + const openTask = (async () => { + // await pending close operation + if (this.closeTask) { + // ignore failed, original close call will reject + await this.closeTask.catch(() => {}) + } + + return new Promise((resolve, reject) => { + this.socket = new WebSocket(this.options.url) + this.socket.binaryType = 'arraybuffer' + this.socket.onopen = (...args) => { + this.debug('opened') + try { + this.emit('open', ...args) + } catch (err) { + reject(err) + return + } + resolve(...args) + } + this.socket.onclose = (code, reason) => { + const msg = `unexpected close. code: ${code}, reason: ${reason}` + this.debug(msg) + reject(new Error(msg)) + this.open() + } + this.socket.onerror = (error, ...args) => { + this.debug(`error: ${error || error.stack}`) + try { + this.emit('error', error, ...args) + } catch (err) { + reject(err) + return + } + reject(error) + } + this.socket.onmessage = (...args) => { + this.emit('message', ...args) + } + }) + })().finally(() => { + // remove openTask if unchanged + if (this.openTask === openTask) { + this.openTask = undefined + } + }) + + this.openTask = openTask + + return this.openTask + } + + async close() { + if (this.isClosed()) { + this.closeTask = undefined + return Promise.resolve() + } + + if (this.closeTask) { + return this.closeTask + } + + const closeTask = (async () => { + // await pending open operation + if (this.openTask) { + // ignore failed, original open call will reject + await this.openTask.catch(() => {}) + } + const { socket } = this + return new Promise((resolve, reject) => { + // replace onclose to resolve/reject closeTask + this.socket.onclose = (...args) => { + this.debug('closed') + if (this.socket === socket) { + // remove socket reference if unchanged + this.socket = undefined + } + try { + this.emit('close', ...args) + } catch (err) { + reject(err) + return + } + resolve(...args) + } + this.socket.onerror = (error, ...args) => { + this.debug(`error: ${error || error.stack}`) + try { + this.emit('error', error, ...args) + } catch (err) { + reject(err) + return + } + reject(error) + } + this.socket.close() + }) + })().finally(() => { + // remove closeTask if unchanged + if (this.closeTask === closeTask) { + this.closeTask = undefined + } + }) + + this.closeTask = closeTask + return this.closeTask + } + + async send(msg) { + if (!this.isOpen()) { + throw new Error('cannot send, not open') + } + return new Promise((resolve, reject) => { + // promisify send + this.socket.send(msg, (err) => { + if (err) { + reject(err) + return + } + resolve(msg) + }) + // send callback doesn't exist with browser websockets, just resolve + if (process.isBrowser) { + resolve(msg) + } + }) + } + + /* + * Status flag methods + */ + + isOpen() { + if (!this.socket) { + return false + } + + return this.socket.readyState === WebSocket.OPEN + } + + isClosed() { + if (!this.socket) { + return true + } + + return this.socket.readyState === WebSocket.CLOSED + } + + isClosing() { + if (!this.socket) { + return false + } + return this.socket.readyState === WebSocket.CLOSING + } + + isOpening() { + if (!this.socket) { + return false + } + return this.socket.readyState === WebSocket.OPENING + } +} diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js new file mode 100644 index 000000000..f9d44138e --- /dev/null +++ b/test/unit/streams/Connection.test.js @@ -0,0 +1,174 @@ +import SocketConnection from '../../../src/streams/SocketConnection' + +describe('SocketConnection', () => { + let s + let onOpen + let onClose + let onError + let onMessage + + beforeEach(() => { + s = new SocketConnection({ + url: 'wss://echo.websocket.org/' + }) + + onOpen = jest.fn() + s.on('open', onOpen) + onClose = jest.fn() + s.on('close', onClose) + onError = jest.fn() + s.on('error', onError) + onMessage = jest.fn() + s.on('message', onMessage) + }) + + afterEach(async () => { + if (!s.isClosed()) { + await s.close() + } + }) + + it('can open & close', async () => { + await s.open() + expect(s.isOpen()).toBeTruthy() + await s.close() + expect(s.isOpen()).toBeFalsy() + expect(s.isClosed()).toBeTruthy() + // check events + expect(onOpen).toHaveBeenCalledTimes(1) + expect(onClose).toHaveBeenCalledTimes(1) + }) + + it('can open after already open', async () => { + await s.open() + await s.open() + expect(s.isOpen()).toBeTruthy() + // only one open event should fire + expect(onOpen).toHaveBeenCalledTimes(1) + }) + + it('can open twice in same tick', async () => { + await Promise.all([ + s.open(), + s.open(), + ]) + expect(s.isOpen()).toBeTruthy() + // only one open event should fire + expect(onOpen).toHaveBeenCalledTimes(1) + }) + + it('can reopen after close', async () => { + await s.open() + expect(s.isOpen()).toBeTruthy() + const oldSocket = s.socket + await s.close() + expect(s.isClosed()).toBeTruthy() + await s.open() + expect(s.isOpen()).toBeTruthy() + // check events + expect(onOpen).toHaveBeenCalledTimes(2) + expect(onClose).toHaveBeenCalledTimes(1) + // ensure new socket + expect(s.socket).not.toBe(oldSocket) + }) + + it('rejects if bad url', async () => { + s = new SocketConnection({ + url: 'badurl' + }) + onOpen = jest.fn() + s.on('open', onOpen) + await expect(async () => { + await s.open() + }).rejects.toThrow('badurl') + expect(onOpen).toHaveBeenCalledTimes(0) + }) + + it('close does not error if never opened', async () => { + expect(s.isClosed()).toBeTruthy() + await s.close() + expect(s.isClosed()).toBeTruthy() + expect(onClose).toHaveBeenCalledTimes(0) + }) + + it('close does not error if already closed', async () => { + await s.open() + await s.close() + expect(s.isClosed()).toBeTruthy() + await s.close() + expect(s.isClosed()).toBeTruthy() + expect(onClose).toHaveBeenCalledTimes(1) + }) + + it('close does not error if already closing', async () => { + await s.open() + await Promise.all([ + s.close(), + s.close(), + ]) + expect(s.isClosed()).toBeTruthy() + expect(onOpen).toHaveBeenCalledTimes(1) + expect(onClose).toHaveBeenCalledTimes(1) + }) + + it('can handle close before open complete', async () => { + await Promise.all([ + s.open(), + s.close() + ]) + expect(s.isClosed()).toBeTruthy() + expect(onOpen).toHaveBeenCalledTimes(1) + expect(onClose).toHaveBeenCalledTimes(1) + }) + + it('can handle open before close complete', async () => { + await s.open() + await Promise.all([ + s.close(), + s.open() + ]) + expect(s.isOpen()).toBeTruthy() + expect(onOpen).toHaveBeenCalledTimes(2) + expect(onClose).toHaveBeenCalledTimes(1) + }) + + it('can try reopen after error', async () => { + const goodUrl = s.options.url + s.options.url = 'badurl' + await expect(async () => ( + s.open() + )).rejects.toThrow('badurl') + await s.close() // shouldn't throw + expect(s.isClosed()).toBeTruthy() + // ensure close + await expect(async () => ( + Promise.all([ + s.open(), + s.close(), + ]) + )).rejects.toThrow('badurl') + // eslint-disable-next-line require-atomic-updates + s.options.url = goodUrl + await s.open() + expect(s.isOpen()).toBeTruthy() + }) + + it('reconnects if unexpectedly disconnected', async (done) => { + await s.open() + s.once('open', () => { + expect(s.isOpen()).toBeTruthy() + done() + }) + s.socket.close() + }) + + it('can send and receive messages', async (done) => { + await s.open() + s.on('message', ({ data } = {}) => { + expect(data).toEqual('test') + done() + }) + + await s.send('test') + }) +}) From 7d09e0fad605de7d916314b22a60a1ae8c4953d9 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 19 Aug 2020 14:02:18 -0400 Subject: [PATCH 020/517] Improve reopening & failure handling. --- src/streams/SocketConnection.js | 89 +++++++++++--------- test/unit/streams/Connection.test.js | 118 +++++++++++++++++++++++---- 2 files changed, 153 insertions(+), 54 deletions(-) diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js index cf1ce7c81..e250cd1c6 100644 --- a/src/streams/SocketConnection.js +++ b/src/streams/SocketConnection.js @@ -6,7 +6,7 @@ import WebSocket from 'ws' /** * Wraps WebSocket open/close with promise methods * adds events - * reconnects on unexpected closure + * reopens on unexpected closure * handles simultaneous calls to open/close * waits for pending close/open before continuing */ @@ -15,7 +15,7 @@ export default class SocketConnection extends EventEmitter { constructor(options) { super() this.options = options - this.attempts = 0 + this.shouldBeOpen = false if (!options.url) { throw new Error('URL is not defined!') } @@ -27,7 +27,13 @@ export default class SocketConnection extends EventEmitter { } } + async tryReopen(...args) { + this.debug('attempting to reopen') + return this.open(...args) + } + async open() { + this.shouldBeOpen = true if (this.isOpen()) { this.openTask = undefined return Promise.resolve() @@ -38,6 +44,7 @@ export default class SocketConnection extends EventEmitter { } const openTask = (async () => { + this.emit('opening') // await pending close operation if (this.closeTask) { // ignore failed, original close call will reject @@ -49,29 +56,31 @@ export default class SocketConnection extends EventEmitter { this.socket.binaryType = 'arraybuffer' this.socket.onopen = (...args) => { this.debug('opened') - try { - this.emit('open', ...args) - } catch (err) { - reject(err) - return - } resolve(...args) + this.emit('open', ...args) } - this.socket.onclose = (code, reason) => { - const msg = `unexpected close. code: ${code}, reason: ${reason}` - this.debug(msg) - reject(new Error(msg)) - this.open() + this.socket.onclose = (event = {}) => { + const { reason, code } = event + this.debug('unexpected close', { + code, + reason, + }) + reject(new Error(`unexpected close. code: ${code}, reason: ${reason}`)) + this.emit('close', event) + this.tryReopen().catch((error) => { + this.debug('error reopening', { + error, + }) + this.emit('error', error) + }) } - this.socket.onerror = (error, ...args) => { - this.debug(`error: ${error || error.stack}`) - try { - this.emit('error', error, ...args) - } catch (err) { - reject(err) - return - } + this.socket.onerror = (err, ...args) => { + const error = err.error || err + this.debug('error while open', { + error, + }) reject(error) + this.emit('error', error, ...args) } this.socket.onmessage = (...args) => { this.emit('message', ...args) @@ -90,6 +99,7 @@ export default class SocketConnection extends EventEmitter { } async close() { + this.shouldBeOpen = false if (this.isClosed()) { this.closeTask = undefined return Promise.resolve() @@ -100,6 +110,7 @@ export default class SocketConnection extends EventEmitter { } const closeTask = (async () => { + this.emit('closing') // await pending open operation if (this.openTask) { // ignore failed, original open call will reject @@ -114,23 +125,16 @@ export default class SocketConnection extends EventEmitter { // remove socket reference if unchanged this.socket = undefined } - try { - this.emit('close', ...args) - } catch (err) { - reject(err) - return - } + resolve(...args) + this.emit('close', ...args) } this.socket.onerror = (error, ...args) => { - this.debug(`error: ${error || error.stack}`) - try { - this.emit('error', error, ...args) - } catch (err) { - reject(err) - return - } + this.debug('error while closing', { + error, + }) reject(error) + this.emit('error', error, ...args) } this.socket.close() }) @@ -145,10 +149,21 @@ export default class SocketConnection extends EventEmitter { return this.closeTask } - async send(msg) { + async waitForOpen() { + if (!this.shouldBeOpen) { + throw new Error('connection closed or closing') + } + if (!this.isOpen()) { - throw new Error('cannot send, not open') + return this.open() } + + return Promise.resolve() + } + + async send(msg) { + await this.waitForOpen() + return new Promise((resolve, reject) => { // promisify send this.socket.send(msg, (err) => { @@ -196,6 +211,6 @@ export default class SocketConnection extends EventEmitter { if (!this.socket) { return false } - return this.socket.readyState === WebSocket.OPENING + return this.socket.readyState === WebSocket.CONNECTING } } diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js index f9d44138e..379c3d038 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/streams/Connection.test.js @@ -1,3 +1,5 @@ +import { wait } from 'streamr-test-utils' + import SocketConnection from '../../../src/streams/SocketConnection' describe('SocketConnection', () => { @@ -23,16 +25,23 @@ describe('SocketConnection', () => { }) afterEach(async () => { - if (!s.isClosed()) { - await s.close() - } + await s.close() }) it('can open & close', async () => { - await s.open() + const openTask = s.open() + expect(s.isOpening()).toBeTruthy() + await openTask + expect(s.isClosed()).toBeFalsy() + expect(s.isClosing()).toBeFalsy() + expect(s.isOpening()).toBeFalsy() expect(s.isOpen()).toBeTruthy() - await s.close() + const closeTask = s.close() + expect(s.isClosing()).toBeTruthy() + await closeTask expect(s.isOpen()).toBeFalsy() + expect(s.isClosing()).toBeFalsy() + expect(s.isOpening()).toBeFalsy() expect(s.isClosed()).toBeTruthy() // check events expect(onOpen).toHaveBeenCalledTimes(1) @@ -84,6 +93,18 @@ describe('SocketConnection', () => { expect(onOpen).toHaveBeenCalledTimes(0) }) + it('rejects if cannot connect', async () => { + s = new SocketConnection({ + url: 'wss://streamr.network/nope' + }) + onOpen = jest.fn() + s.on('open', onOpen) + await expect(async () => { + await s.open() + }).rejects.toThrow('Unexpected server response') + expect(onOpen).toHaveBeenCalledTimes(0) + }) + it('close does not error if never opened', async () => { expect(s.isClosed()).toBeTruthy() await s.close() @@ -132,6 +153,17 @@ describe('SocketConnection', () => { expect(onClose).toHaveBeenCalledTimes(1) }) + it('fails if error connecting', async () => { + await s.open() + await Promise.all([ + s.close(), + s.open() + ]) + expect(s.isOpen()).toBeTruthy() + expect(onOpen).toHaveBeenCalledTimes(2) + expect(onClose).toHaveBeenCalledTimes(1) + }) + it('can try reopen after error', async () => { const goodUrl = s.options.url s.options.url = 'badurl' @@ -153,22 +185,74 @@ describe('SocketConnection', () => { expect(s.isOpen()).toBeTruthy() }) - it('reconnects if unexpectedly disconnected', async (done) => { - await s.open() - s.once('open', () => { - expect(s.isOpen()).toBeTruthy() - done() + describe('reopening', () => { + it('reopens if unexpectedly disconnected', async (done) => { + await s.open() + s.once('open', () => { + expect(s.isOpen()).toBeTruthy() + done() + }) + s.socket.close() + }) + + it('fails if reopen fails', async (done) => { + await s.open() + // eslint-disable-next-line require-atomic-updates + s.options.url = 'badurl' + s.once('error', (err) => { + expect(err).toBeTruthy() + expect(onOpen).toHaveBeenCalledTimes(1) + expect(s.isClosed()).toBeTruthy() + done() + }) + s.socket.close() }) - s.socket.close() }) - it('can send and receive messages', async (done) => { - await s.open() - s.on('message', ({ data } = {}) => { - expect(data).toEqual('test') - done() + describe('send', () => { + it('can send and receive messages', async (done) => { + await s.open() + s.once('message', ({ data } = {}) => { + expect(data).toEqual('test') + done() + }) + + await s.send('test') }) - await s.send('test') + it('waits for reopening if sending while reopening', async (done) => { + await s.open() + const open = s.open.bind(s) + s.open = async (...args) => { + await wait(0) + return open(...args) + } + + s.once('message', ({ data } = {}) => { + expect(data).toEqual('test') + done() + }) + + s.socket.close() + await s.send('test') + }) + + it('fails send if reopen fails', async () => { + await s.open() + // eslint-disable-next-line require-atomic-updates + s.options.url = 'badurl' + s.socket.close() + await expect(async () => { + await s.send('test') + }).rejects.toThrow('badurl') + }) + + it('fails send if intentionally closed', async () => { + await s.open() + await s.close() + await expect(async () => { + await s.send('test') + }).rejects.toThrow() + }) }) }) From 801f3a9d5f50740a162fd02b19dd8f23a938877c Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 21 Aug 2020 10:37:03 -0400 Subject: [PATCH 021/517] Split reopening logic into SocketConnection subclass. --- src/streams/SocketConnection.js | 141 ++++++++++++++++++++++----- test/unit/streams/Connection.test.js | 120 ++++++++++++++++++----- 2 files changed, 211 insertions(+), 50 deletions(-) diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js index e250cd1c6..06cf93d0a 100644 --- a/src/streams/SocketConnection.js +++ b/src/streams/SocketConnection.js @@ -6,16 +6,14 @@ import WebSocket from 'ws' /** * Wraps WebSocket open/close with promise methods * adds events - * reopens on unexpected closure * handles simultaneous calls to open/close * waits for pending close/open before continuing */ -export default class SocketConnection extends EventEmitter { +class SocketConnection extends EventEmitter { constructor(options) { super() this.options = options - this.shouldBeOpen = false if (!options.url) { throw new Error('URL is not defined!') } @@ -27,13 +25,7 @@ export default class SocketConnection extends EventEmitter { } } - async tryReopen(...args) { - this.debug('attempting to reopen') - return this.open(...args) - } - async open() { - this.shouldBeOpen = true if (this.isOpen()) { this.openTask = undefined return Promise.resolve() @@ -67,12 +59,6 @@ export default class SocketConnection extends EventEmitter { }) reject(new Error(`unexpected close. code: ${code}, reason: ${reason}`)) this.emit('close', event) - this.tryReopen().catch((error) => { - this.debug('error reopening', { - error, - }) - this.emit('error', error) - }) } this.socket.onerror = (err, ...args) => { const error = err.error || err @@ -99,7 +85,6 @@ export default class SocketConnection extends EventEmitter { } async close() { - this.shouldBeOpen = false if (this.isClosed()) { this.closeTask = undefined return Promise.resolve() @@ -149,20 +134,13 @@ export default class SocketConnection extends EventEmitter { return this.closeTask } - async waitForOpen() { + async send(msg) { if (!this.shouldBeOpen) { throw new Error('connection closed or closing') } - if (!this.isOpen()) { - return this.open() - } - - return Promise.resolve() - } - - async send(msg) { - await this.waitForOpen() + // should be open, so wait for open or trigger new open + await this.open() return new Promise((resolve, reject) => { // promisify send @@ -214,3 +192,114 @@ export default class SocketConnection extends EventEmitter { return this.socket.readyState === WebSocket.CONNECTING } } + +/** + * Extends SocketConnection to include reopening logic. + */ + +export default class ManagedSocketConnection extends SocketConnection { + constructor(...args) { + super(...args) + this.options.maxRetries = this.options.maxRetries != null ? this.options.maxRetries : 10 + this.options.retryBackoffFactor = this.options.retryBackoffFactor != null ? this.options.retryBackoffFactor : 1.2 + this.options.maxRetryWait = this.options.maxRetryWait != null ? this.options.maxRetryWait : 10000 + this.reopenOnClose = this.reopenOnClose.bind(this) + this.retryCount = 1 + this.isReopening = false + } + + async backoffWait() { + return new Promise((resolve) => { + const timeout = Math.min( + this.options.maxRetryWait, // max wait time + Math.round((this.retryCount * 10) ** this.options.retryBackoffFactor) + ) + this.debug('waiting %sms', timeout) + setTimeout(resolve, timeout) + }) + } + + async reopen(...args) { + await this.reopenTask + this.reopenTask = (async () => { + // closed, noop + if (!this.shouldBeOpen) { + this.isReopening = false + return Promise.resolve() + } + this.isReopening = true + // wait for a moment + await this.backoffWait() + + // re-check if closed or closing + if (!this.shouldBeOpen) { + this.isReopening = false + return Promise.resolve() + } + + this.emit('retry') + this.debug('attempting to reopen %s of %s', this.retryCount, this.options.maxRetries) + + return this._open(...args).then((value) => { + // reset retry state + this.reopenTask = undefined + this.retryCount = 1 + this.isReopening = false + return value + }, (err) => { + this.debug('attempt to reopen %s of %s failed', this.retryCount, this.options.maxRetries, err) + this.reopenTask = undefined + this.retryCount += 1 + if (this.retryCount > this.options.maxRetries) { + // no more retries + this.isReopening = false + throw err + } + // try again + return this.reopen() + }) + })() + return this.reopenTask + } + + async reopenOnClose() { + if (!this.shouldBeOpen) { + return Promise.resolve() + } + + return this.reopen().catch((error) => { + this.debug('failed reopening', { + error, + }) + this.emit('error', error) + }) + } + + /** + * Call this internally so as to not mess with user intent shouldBeOpen + */ + + _open(...args) { + if (!this.shouldBeOpen) { + // shouldn't get here + throw new Error('cannot tryOpen, connection closed or closing') + } + + this.removeListener('close', this.reopenOnClose) + return super.open(...args).then((value) => { + this.on('close', this.reopenOnClose) // try reopen on close unless purposely closed + return value + }) + } + + open(...args) { + this.shouldBeOpen = true // user intent + return this._open(...args) + } + + close(...args) { + this.shouldBeOpen = false // user intent + this.removeListener('close', this.reopenOnClose) + return super.close(...args) + } +} diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js index 379c3d038..9c0c2f57c 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/streams/Connection.test.js @@ -2,6 +2,8 @@ import { wait } from 'streamr-test-utils' import SocketConnection from '../../../src/streams/SocketConnection' +/* eslint-disable require-atomic-updates */ + describe('SocketConnection', () => { let s let onOpen @@ -11,7 +13,8 @@ describe('SocketConnection', () => { beforeEach(() => { s = new SocketConnection({ - url: 'wss://echo.websocket.org/' + url: 'wss://echo.websocket.org/', + maxRetries: 5 }) onOpen = jest.fn() @@ -164,27 +167,6 @@ describe('SocketConnection', () => { expect(onClose).toHaveBeenCalledTimes(1) }) - it('can try reopen after error', async () => { - const goodUrl = s.options.url - s.options.url = 'badurl' - await expect(async () => ( - s.open() - )).rejects.toThrow('badurl') - await s.close() // shouldn't throw - expect(s.isClosed()).toBeTruthy() - // ensure close - await expect(async () => ( - Promise.all([ - s.open(), - s.close(), - ]) - )).rejects.toThrow('badurl') - // eslint-disable-next-line require-atomic-updates - s.options.url = goodUrl - await s.open() - expect(s.isOpen()).toBeTruthy() - }) - describe('reopening', () => { it('reopens if unexpectedly disconnected', async (done) => { await s.open() @@ -195,9 +177,8 @@ describe('SocketConnection', () => { s.socket.close() }) - it('fails if reopen fails', async (done) => { + it('errors if reopen fails', async (done) => { await s.open() - // eslint-disable-next-line require-atomic-updates s.options.url = 'badurl' s.once('error', (err) => { expect(err).toBeTruthy() @@ -207,6 +188,97 @@ describe('SocketConnection', () => { }) s.socket.close() }) + + it('retries multiple times when disconnected', async (done) => { + /* eslint-disable no-underscore-dangle */ + await s.open() + const goodUrl = s.options.url + let retryCount = 0 + s.options.url = 'badurl' + s.on('retry', () => { + retryCount += 1 + // fail first 3 tries + // pass after + if (retryCount >= 3) { + s.options.url = goodUrl + } + }) + s.once('open', () => { + expect(s.isOpen()).toBeTruthy() + expect(retryCount).toEqual(3) + done() + }) + s.socket.close() + /* eslint-enable no-underscore-dangle */ + }) + + it('fails if exceed max retries', async (done) => { + await s.open() + s.options.maxRetries = 2 + s.options.url = 'badurl' + s.once('error', (err) => { + expect(err).toBeTruthy() + done() + }) + s.socket.close() + }) + + it('resets max retries on manual open after failure', async (done) => { + await s.open() + const goodUrl = s.options.url + s.options.maxRetries = 2 + s.options.url = 'badurl' + s.once('error', async (err) => { + expect(err).toBeTruthy() + s.options.url = goodUrl + await s.open() + setTimeout(() => { + expect(s.isReopening).toBeFalsy() + expect(s.isOpen()).toBeTruthy() + done() + }) + }) + s.socket.close() + }) + + it('can try reopen after error', async () => { + const goodUrl = s.options.url + s.options.url = 'badurl' + await expect(async () => ( + s.open() + )).rejects.toThrow('badurl') + await s.close() // shouldn't throw + expect(s.isClosed()).toBeTruthy() + // ensure close + await expect(async () => ( + Promise.all([ + s.open(), + s.close(), + ]) + )).rejects.toThrow('badurl') + s.options.url = goodUrl + await s.open() + expect(s.isOpen()).toBeTruthy() + }) + + it('stops reopening if closed while reopening', async (done) => { + await s.open() + const goodUrl = s.options.url + s.options.url = 'badurl' + // once closed due to error, actually close + s.once('close', async () => { + s.options.url = goodUrl + await s.close() + // wait a moment + setTimeout(() => { + // ensure is closed, not reopening + expect(s.isClosed()).toBeTruthy() + expect(s.isReopening).toBeFalsy() + done() + }, 10) + }) + s.socket.close() + }) }) describe('send', () => { From a6cc8c5a57dca550cf3b39ab3e52721a37e0f5bd Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 21 Aug 2020 12:57:32 -0400 Subject: [PATCH 022/517] Refactor open/close logic, move no url error to open call to be consistent with bad url parsing errors. --- src/streams/SocketConnection.js | 117 ++++++++++++++------------- test/unit/streams/Connection.test.js | 37 +++++++++ 2 files changed, 96 insertions(+), 58 deletions(-) diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js index 06cf93d0a..97050b406 100644 --- a/src/streams/SocketConnection.js +++ b/src/streams/SocketConnection.js @@ -14,9 +14,6 @@ class SocketConnection extends EventEmitter { constructor(options) { super() this.options = options - if (!options.url) { - throw new Error('URL is not defined!') - } const id = uniqueId('SocketConnection') if (options.debug) { this.debug = options.debug.extend(id) @@ -25,6 +22,62 @@ class SocketConnection extends EventEmitter { } } + async createSocket() { + return new Promise((resolve, reject) => { + if (!this.options.url) { + throw new Error('URL is not defined!') + } + this.socket = new WebSocket(this.options.url) + this.socket.binaryType = 'arraybuffer' + this.socket.onopen = (...args) => { + this.debug('opened') + resolve(...args) + this.emit('open', ...args) + } + this.socket.onclose = (event = {}) => { + const { reason, code } = event + this.debug('unexpected close. code: %s reason: %s', code, reason) + reject(new Error(`unexpected close. code: ${code}, reason: ${reason}`)) + this.emit('close', event) + } + this.socket.onerror = (err, ...args) => { + const error = err.error || err + this.debug('error while open', error) + reject(error) + this.emit('error', error, ...args) + } + this.socket.onmessage = (...args) => { + this.emit('message', ...args) + } + }) + } + + async closeSocket() { + const { socket } = this + return new Promise((resolve, reject) => { + // replace onclose to resolve/reject closeTask + this.socket.onclose = (event = {}, ...args) => { + const { reason, code } = event + this.debug('closed. code: %s reason: %s', code, reason) + + if (this.socket === socket) { + // remove socket reference if unchanged + this.socket = undefined + } + + resolve(event) + this.emit('close', event, ...args) + } + this.socket.onerror = (error, ...args) => { + // not sure it's even possible to have an error fire during close + this.debug('error while closing', error) + reject(error) + this.emit('error', error, ...args) + } + this.socket.close() + }) + } + async open() { if (this.isOpen()) { this.openTask = undefined @@ -42,36 +95,7 @@ class SocketConnection extends EventEmitter { // ignore failed, original close call will reject await this.closeTask.catch(() => {}) } - - return new Promise((resolve, reject) => { - this.socket = new WebSocket(this.options.url) - this.socket.binaryType = 'arraybuffer' - this.socket.onopen = (...args) => { - this.debug('opened') - resolve(...args) - this.emit('open', ...args) - } - this.socket.onclose = (event = {}) => { - const { reason, code } = event - this.debug('unexpected close', { - code, - reason, - }) - reject(new Error(`unexpected close. code: ${code}, reason: ${reason}`)) - this.emit('close', event) - } - this.socket.onerror = (err, ...args) => { - const error = err.error || err - this.debug('error while open', { - error, - }) - reject(error) - this.emit('error', error, ...args) - } - this.socket.onmessage = (...args) => { - this.emit('message', ...args) - } - }) + return this.createSocket() })().finally(() => { // remove openTask if unchanged if (this.openTask === openTask) { @@ -101,28 +125,7 @@ class SocketConnection extends EventEmitter { // ignore failed, original open call will reject await this.openTask.catch(() => {}) } - const { socket } = this - return new Promise((resolve, reject) => { - // replace onclose to resolve/reject closeTask - this.socket.onclose = (...args) => { - this.debug('closed') - if (this.socket === socket) { - // remove socket reference if unchanged - this.socket = undefined - } - - resolve(...args) - this.emit('close', ...args) - } - this.socket.onerror = (error, ...args) => { - this.debug('error while closing', { - error, - }) - reject(error) - this.emit('error', error, ...args) - } - this.socket.close() - }) + return this.closeSocket() })().finally(() => { // remove closeTask if unchanged if (this.closeTask === closeTask) { @@ -268,9 +271,7 @@ export default class ManagedSocketConnection extends SocketConnection { } return this.reopen().catch((error) => { - this.debug('failed reopening', { - error, - }) + this.debug('failed reopening', error) this.emit('error', error) }) } diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js index 9c0c2f57c..5f5a17564 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/streams/Connection.test.js @@ -7,6 +7,7 @@ import SocketConnection from '../../../src/streams/SocketConnection' describe('SocketConnection', () => { let s let onOpen + let onOpening let onClose let onError let onMessage @@ -19,6 +20,8 @@ describe('SocketConnection', () => { onOpen = jest.fn() s.on('open', onOpen) + onOpening = jest.fn() + s.on('opening', onOpening) onClose = jest.fn() s.on('close', onClose) onError = jest.fn() @@ -67,6 +70,7 @@ describe('SocketConnection', () => { expect(s.isOpen()).toBeTruthy() // only one open event should fire expect(onOpen).toHaveBeenCalledTimes(1) + expect(onOpening).toHaveBeenCalledTimes(1) }) it('can reopen after close', async () => { @@ -84,6 +88,18 @@ describe('SocketConnection', () => { expect(s.socket).not.toBe(oldSocket) }) + it('rejects if no url', async () => { + s = new SocketConnection({ + url: undefined, + }) + onOpen = jest.fn() + s.on('open', onOpen) + await expect(async () => { + await s.open() + }).rejects.toThrow('not defined') + expect(onOpen).toHaveBeenCalledTimes(0) + }) + it('rejects if bad url', async () => { s = new SocketConnection({ url: 'badurl' @@ -279,6 +295,27 @@ describe('SocketConnection', () => { }) s.socket.close() }) + it('stops reopening if closed while reopening, after some delay', async (done) => { + await s.open() + const goodUrl = s.options.url + s.options.url = 'badurl' + // once closed due to error, actually close + s.once('close', async () => { + s.options.url = goodUrl + await s.close() + // wait a moment + setTimeout(() => { + // ensure is closed, not reopening + expect(s.isClosed()).toBeTruthy() + expect(s.isReopening).toBeFalsy() + done() + }, 10) + }) + // some delay + setTimeout(() => { + s.socket.close() + }, 10) + }) }) describe('send', () => { From fa1e1b5ad3bcede61d4eee76c8fdde1caf0d38d0 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 21 Aug 2020 12:58:54 -0400 Subject: [PATCH 023/517] Convert event handler exceptions to error events. Tidy code coverage. --- src/streams/SocketConnection.js | 22 +++++++++++++++ test/unit/streams/Connection.test.js | 41 ++++++++++++++++++++-------- 2 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js index 97050b406..a8be465d1 100644 --- a/src/streams/SocketConnection.js +++ b/src/streams/SocketConnection.js @@ -15,6 +15,7 @@ class SocketConnection extends EventEmitter { super() this.options = options const id = uniqueId('SocketConnection') + /* istanbul ignore next */ if (options.debug) { this.debug = options.debug.extend(id) } else { @@ -22,6 +23,22 @@ class SocketConnection extends EventEmitter { } } + emit(event, ...args) { + if (event === 'error') { + return super.emit(event, ...args) + } + + // note if event handler is async and it rejects we're kinda hosed + // until node lands unhandledrejection support + // in eventemitter + try { + return super.emit(event, ...args) + } catch (err) { + super.emit('error', err) + return true + } + } + async createSocket() { return new Promise((resolve, reject) => { if (!this.options.url) { @@ -68,6 +85,8 @@ class SocketConnection extends EventEmitter { resolve(event) this.emit('close', event, ...args) } + + /* istanbul ignore next */ this.socket.onerror = (error, ...args) => { // not sure it's even possible to have an error fire during close this.debug('error while closing', error) @@ -148,6 +167,7 @@ class SocketConnection extends EventEmitter { return new Promise((resolve, reject) => { // promisify send this.socket.send(msg, (err) => { + /* istanbul ignore next */ if (err) { reject(err) return @@ -155,6 +175,7 @@ class SocketConnection extends EventEmitter { resolve(msg) }) // send callback doesn't exist with browser websockets, just resolve + /* istanbul ignore next */ if (process.isBrowser) { resolve(msg) } @@ -281,6 +302,7 @@ export default class ManagedSocketConnection extends SocketConnection { */ _open(...args) { + /* istanbul ignore next */ if (!this.shouldBeOpen) { // shouldn't get here throw new Error('cannot tryOpen, connection closed or closing') diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js index 5f5a17564..c584f10fc 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/streams/Connection.test.js @@ -183,6 +183,21 @@ describe('SocketConnection', () => { expect(onClose).toHaveBeenCalledTimes(1) }) + it('emits error but does not close if open event handler fails', async (done) => { + const error = new Error('expected error') + s.once('open', () => { + throw error + }) + s.once('error', async (err) => { + expect(err).toBe(error) + await wait() + expect(s.isOpen()).toBeTruthy() + done() + }) + await s.open() + expect(s.isOpen()).toBeTruthy() + }) + describe('reopening', () => { it('reopens if unexpectedly disconnected', async (done) => { await s.open() @@ -283,6 +298,7 @@ describe('SocketConnection', () => { s.options.url = 'badurl' // once closed due to error, actually close s.once('close', async () => { + // i.e. would reconnect if not closing s.options.url = goodUrl await s.close() // wait a moment @@ -293,28 +309,31 @@ describe('SocketConnection', () => { done() }, 10) }) + // trigger reopening cycle s.socket.close() }) + it('stops reopening if closed while reopening, after some delay', async (done) => { await s.open() const goodUrl = s.options.url s.options.url = 'badurl' // once closed due to error, actually close s.once('close', async () => { - s.options.url = goodUrl - await s.close() // wait a moment - setTimeout(() => { - // ensure is closed, not reopening - expect(s.isClosed()).toBeTruthy() - expect(s.isReopening).toBeFalsy() - done() + setTimeout(async () => { + // i.e. would reconnect if not closing + s.options.url = goodUrl + await s.close() + setTimeout(async () => { + // ensure is closed, not reopening + expect(s.isClosed()).toBeTruthy() + expect(s.isReopening).toBeFalsy() + done() + }, 20) }, 10) }) - // some delay - setTimeout(() => { - s.socket.close() - }, 10) + // trigger reopening cycle + s.socket.close() }) }) From 18b33aec6a182fd04bf039951b8f344d28faf93b Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Sat, 22 Aug 2020 17:59:38 -0400 Subject: [PATCH 024/517] Simplify SocketConnection. --- src/streams/SocketConnection.js | 383 ++++++++++++--------------- test/unit/streams/Connection.test.js | 111 ++++---- 2 files changed, 221 insertions(+), 273 deletions(-) diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js index a8be465d1..db73325f5 100644 --- a/src/streams/SocketConnection.js +++ b/src/streams/SocketConnection.js @@ -10,10 +10,16 @@ import WebSocket from 'ws' * waits for pending close/open before continuing */ -class SocketConnection extends EventEmitter { +export default class SocketConnection extends EventEmitter { constructor(options) { super() this.options = options + this.options.maxRetries = this.options.maxRetries != null ? this.options.maxRetries : 10 + this.options.retryBackoffFactor = this.options.retryBackoffFactor != null ? this.options.retryBackoffFactor : 1.2 + this.options.maxRetryWait = this.options.maxRetryWait != null ? this.options.maxRetryWait : 10000 + this.shouldConnect = false + this.retryCount = 1 + this.isReopening = false const id = uniqueId('SocketConnection') /* istanbul ignore next */ if (options.debug) { @@ -23,6 +29,17 @@ class SocketConnection extends EventEmitter { } } + async backoffWait() { + return new Promise((resolve) => { + const timeout = Math.min( + this.options.maxRetryWait, // max wait time + Math.round((this.retryCount * 10) ** this.options.retryBackoffFactor) + ) + this.debug('waiting %sms', timeout) + setTimeout(resolve, timeout) + }) + } + emit(event, ...args) { if (event === 'error') { return super.emit(event, ...args) @@ -39,130 +56,182 @@ class SocketConnection extends EventEmitter { } } - async createSocket() { - return new Promise((resolve, reject) => { - if (!this.options.url) { - throw new Error('URL is not defined!') - } - this.socket = new WebSocket(this.options.url) - this.socket.binaryType = 'arraybuffer' - this.socket.onopen = (...args) => { - this.debug('opened') - resolve(...args) - this.emit('open', ...args) - } - this.socket.onclose = (event = {}) => { - const { reason, code } = event - this.debug('unexpected close. code: %s reason: %s', code, reason) - reject(new Error(`unexpected close. code: ${code}, reason: ${reason}`)) - this.emit('close', event) - } - this.socket.onerror = (err, ...args) => { - const error = err.error || err - this.debug('error while open', error) - reject(error) - this.emit('error', error, ...args) + async reopen(...args) { + await this.reopenTask + this.reopenTask = (async () => { + // closed, noop + if (!this.shouldConnect) { + this.isReopening = false + return Promise.resolve() } - this.socket.onmessage = (...args) => { - this.emit('message', ...args) + this.isReopening = true + // wait for a moment + await this.backoffWait() + + // re-check if closed or closing + if (!this.shouldConnect) { + this.isReopening = false + return Promise.resolve() } - }) - } - async closeSocket() { - const { socket } = this - return new Promise((resolve, reject) => { - // replace onclose to resolve/reject closeTask - this.socket.onclose = (event = {}, ...args) => { - const { reason, code } = event - this.debug('closed. code: %s reason: %s', code, reason) - - if (this.socket === socket) { - // remove socket reference if unchanged - this.socket = undefined - } + this.emit('retry') + this.debug('attempting to reopen %s of %s', this.retryCount, this.options.maxRetries) - resolve(event) - this.emit('close', event, ...args) - } + return this._connect(...args).then((value) => { + // reset retry state + this.reopenTask = undefined + this.retryCount = 1 + this.isReopening = false + return value + }, (err) => { + this.debug('attempt to reopen %s of %s failed', this.retryCount, this.options.maxRetries, err) + this.reopenTask = undefined + this.retryCount += 1 + if (this.retryCount > this.options.maxRetries) { + // no more retries + this.isReopening = false + throw err + } + // try again + return this.reopen() + }) + })() + return this.reopenTask + } - /* istanbul ignore next */ - this.socket.onerror = (error, ...args) => { - // not sure it's even possible to have an error fire during close - this.debug('error while closing', error) - reject(error) - this.emit('error', error, ...args) - } - this.socket.close() - }) + async connect() { + this.shouldConnect = true + return this._connect() } - async open() { - if (this.isOpen()) { - this.openTask = undefined - return Promise.resolve() - } + async _connect() { + return new Promise((resolve, reject) => { + try { + if (!this.shouldConnect) { + reject(new Error('disconnected before connected')) + return + } + let { socket } = this + const isNew = !socket + if (!socket) { + if (!this.options.url) { + throw new Error('URL is not defined!') + } + socket = new WebSocket(this.options.url) + socket.binaryType = 'arraybuffer' + this.socket = socket + } - if (this.openTask) { - return this.openTask - } + socket.addEventListener('close', () => { + if (this.shouldConnect) { + this.reopen().then(resolve).catch((error) => { + this.debug('failed reopening', error) + this.emit('error', error) + reject(error) + }) + } + }) + + socket.addEventListener('open', () => { + if (!this.shouldConnect) { + reject(new Error('disconnected before connected')) + return + } + resolve() + }) + + socket.addEventListener('error', (err) => { + const error = err.error || new Error(err) + reject(error) + }) + + if (socket.readyState === WebSocket.OPEN) { + resolve() + } - const openTask = (async () => { - this.emit('opening') - // await pending close operation - if (this.closeTask) { - // ignore failed, original close call will reject - await this.closeTask.catch(() => {}) - } - return this.createSocket() - })().finally(() => { - // remove openTask if unchanged - if (this.openTask === openTask) { - this.openTask = undefined + if (isNew) { + this.emit('opening') + socket.addEventListener('message', (...args) => { + if (this.socket !== socket) { return } + this.emit('message', ...args) + }) + socket.addEventListener('open', (event) => { + if (this.socket !== socket) { return } + this.emit('open', event) + }) + socket.addEventListener('close', (event) => { + if (this.socket === socket) { + this.socket = undefined + this.emit('close', event) + } + }) + socket.addEventListener('error', (err) => { + if (this.socket !== socket) { return } + const error = err.error || new Error(err) + this.emit('error', error) + }) + } + } catch (err) { + reject(err) } }) + } - this.openTask = openTask - - return this.openTask + async disconnect() { + this.shouldConnect = false + return this._disconnect() } - async close() { - if (this.isClosed()) { - this.closeTask = undefined - return Promise.resolve() - } + async _disconnect() { + return new Promise((resolve, reject) => { + try { + if (this.shouldConnect) { + reject(new Error('connected before disconnected')) + return + } - if (this.closeTask) { - return this.closeTask - } + const { socket } = this + if (!socket || socket.readyState === WebSocket.CLOSED) { + resolve() + return + } - const closeTask = (async () => { - this.emit('closing') - // await pending open operation - if (this.openTask) { - // ignore failed, original open call will reject - await this.openTask.catch(() => {}) - } - return this.closeSocket() - })().finally(() => { - // remove closeTask if unchanged - if (this.closeTask === closeTask) { - this.closeTask = undefined + socket.addEventListener('open', () => { + if (!this.shouldConnect) { + resolve(this._disconnect()) + } + }) + + socket.addEventListener('error', (err) => { + const error = err.error || new Error(err) + reject(error) + }) + socket.addEventListener('close', () => { + if (this.shouldConnect) { + reject(new Error('connected before disconnected')) + return + } + resolve() + }) + + this.emit('closing') + + if (socket.readyState === WebSocket.OPEN) { + socket.close() + } + } catch (err) { + reject(err) } }) - - this.closeTask = closeTask - return this.closeTask } async send(msg) { - if (!this.shouldBeOpen) { + if (!this.shouldConnect || !this.socket) { throw new Error('connection closed or closing') } // should be open, so wait for open or trigger new open - await this.open() + await this.connect() return new Promise((resolve, reject) => { // promisify send @@ -182,10 +251,6 @@ class SocketConnection extends EventEmitter { }) } - /* - * Status flag methods - */ - isOpen() { if (!this.socket) { return false @@ -216,113 +281,3 @@ class SocketConnection extends EventEmitter { return this.socket.readyState === WebSocket.CONNECTING } } - -/** - * Extends SocketConnection to include reopening logic. - */ - -export default class ManagedSocketConnection extends SocketConnection { - constructor(...args) { - super(...args) - this.options.maxRetries = this.options.maxRetries != null ? this.options.maxRetries : 10 - this.options.retryBackoffFactor = this.options.retryBackoffFactor != null ? this.options.retryBackoffFactor : 1.2 - this.options.maxRetryWait = this.options.maxRetryWait != null ? this.options.maxRetryWait : 10000 - this.reopenOnClose = this.reopenOnClose.bind(this) - this.retryCount = 1 - this.isReopening = false - } - - async backoffWait() { - return new Promise((resolve) => { - const timeout = Math.min( - this.options.maxRetryWait, // max wait time - Math.round((this.retryCount * 10) ** this.options.retryBackoffFactor) - ) - this.debug('waiting %sms', timeout) - setTimeout(resolve, timeout) - }) - } - - async reopen(...args) { - await this.reopenTask - this.reopenTask = (async () => { - // closed, noop - if (!this.shouldBeOpen) { - this.isReopening = false - return Promise.resolve() - } - this.isReopening = true - // wait for a moment - await this.backoffWait() - - // re-check if closed or closing - if (!this.shouldBeOpen) { - this.isReopening = false - return Promise.resolve() - } - - this.emit('retry') - this.debug('attempting to reopen %s of %s', this.retryCount, this.options.maxRetries) - - return this._open(...args).then((value) => { - // reset retry state - this.reopenTask = undefined - this.retryCount = 1 - this.isReopening = false - return value - }, (err) => { - this.debug('attempt to reopen %s of %s failed', this.retryCount, this.options.maxRetries, err) - this.reopenTask = undefined - this.retryCount += 1 - if (this.retryCount > this.options.maxRetries) { - // no more retries - this.isReopening = false - throw err - } - // try again - return this.reopen() - }) - })() - return this.reopenTask - } - - async reopenOnClose() { - if (!this.shouldBeOpen) { - return Promise.resolve() - } - - return this.reopen().catch((error) => { - this.debug('failed reopening', error) - this.emit('error', error) - }) - } - - /** - * Call this internally so as to not mess with user intent shouldBeOpen - */ - - _open(...args) { - /* istanbul ignore next */ - if (!this.shouldBeOpen) { - // shouldn't get here - throw new Error('cannot tryOpen, connection closed or closing') - } - - this.removeListener('close', this.reopenOnClose) - return super.open(...args).then((value) => { - this.on('close', this.reopenOnClose) // try reopen on close unless purposely closed - return value - }) - } - - open(...args) { - this.shouldBeOpen = true // user intent - return this._open(...args) - } - - close(...args) { - this.shouldBeOpen = false // user intent - this.removeListener('close', this.reopenOnClose) - return super.close(...args) - } -} diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js index c584f10fc..eeb0e77ab 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/streams/Connection.test.js @@ -31,18 +31,18 @@ describe('SocketConnection', () => { }) afterEach(async () => { - await s.close() + await s.disconnect() }) it('can open & close', async () => { - const openTask = s.open() + const openTask = s.connect() expect(s.isOpening()).toBeTruthy() await openTask expect(s.isClosed()).toBeFalsy() expect(s.isClosing()).toBeFalsy() expect(s.isOpening()).toBeFalsy() expect(s.isOpen()).toBeTruthy() - const closeTask = s.close() + const closeTask = s.disconnect() expect(s.isClosing()).toBeTruthy() await closeTask expect(s.isOpen()).toBeFalsy() @@ -55,8 +55,8 @@ describe('SocketConnection', () => { }) it('can open after already open', async () => { - await s.open() - await s.open() + await s.connect() + await s.connect() expect(s.isOpen()).toBeTruthy() // only one open event should fire expect(onOpen).toHaveBeenCalledTimes(1) @@ -64,8 +64,8 @@ describe('SocketConnection', () => { it('can open twice in same tick', async () => { await Promise.all([ - s.open(), - s.open(), + s.connect(), + s.connect(), ]) expect(s.isOpen()).toBeTruthy() // only one open event should fire @@ -74,12 +74,12 @@ describe('SocketConnection', () => { }) it('can reopen after close', async () => { - await s.open() + await s.connect() expect(s.isOpen()).toBeTruthy() const oldSocket = s.socket - await s.close() + await s.disconnect() expect(s.isClosed()).toBeTruthy() - await s.open() + await s.connect() expect(s.isOpen()).toBeTruthy() // check events expect(onOpen).toHaveBeenCalledTimes(2) @@ -95,7 +95,7 @@ describe('SocketConnection', () => { onOpen = jest.fn() s.on('open', onOpen) await expect(async () => { - await s.open() + await s.connect() }).rejects.toThrow('not defined') expect(onOpen).toHaveBeenCalledTimes(0) }) @@ -107,7 +107,7 @@ describe('SocketConnection', () => { onOpen = jest.fn() s.on('open', onOpen) await expect(async () => { - await s.open() + await s.connect() }).rejects.toThrow('badurl') expect(onOpen).toHaveBeenCalledTimes(0) }) @@ -119,32 +119,32 @@ describe('SocketConnection', () => { onOpen = jest.fn() s.on('open', onOpen) await expect(async () => { - await s.open() + await s.connect() }).rejects.toThrow('Unexpected server response') expect(onOpen).toHaveBeenCalledTimes(0) }) it('close does not error if never opened', async () => { expect(s.isClosed()).toBeTruthy() - await s.close() + await s.disconnect() expect(s.isClosed()).toBeTruthy() expect(onClose).toHaveBeenCalledTimes(0) }) it('close does not error if already closed', async () => { - await s.open() - await s.close() + await s.connect() + await s.disconnect() expect(s.isClosed()).toBeTruthy() - await s.close() + await s.disconnect() expect(s.isClosed()).toBeTruthy() expect(onClose).toHaveBeenCalledTimes(1) }) it('close does not error if already closing', async () => { - await s.open() + await s.connect() await Promise.all([ - s.close(), - s.close(), + s.disconnect(), + s.disconnect(), ]) expect(s.isClosed()).toBeTruthy() expect(onOpen).toHaveBeenCalledTimes(1) @@ -153,8 +153,10 @@ describe('SocketConnection', () => { it('can handle close before open complete', async () => { await Promise.all([ - s.open(), - s.close() + expect(async () => ( + s.connect() + )).rejects.toThrow(), + s.disconnect() ]) expect(s.isClosed()).toBeTruthy() expect(onOpen).toHaveBeenCalledTimes(1) @@ -162,21 +164,12 @@ describe('SocketConnection', () => { }) it('can handle open before close complete', async () => { - await s.open() + await s.connect() await Promise.all([ - s.close(), - s.open() - ]) - expect(s.isOpen()).toBeTruthy() - expect(onOpen).toHaveBeenCalledTimes(2) - expect(onClose).toHaveBeenCalledTimes(1) - }) - - it('fails if error connecting', async () => { - await s.open() - await Promise.all([ - s.close(), - s.open() + expect(async () => ( + s.disconnect() + )).rejects.toThrow(), + s.connect() ]) expect(s.isOpen()).toBeTruthy() expect(onOpen).toHaveBeenCalledTimes(2) @@ -194,13 +187,13 @@ describe('SocketConnection', () => { expect(s.isOpen()).toBeTruthy() done() }) - await s.open() + await s.connect() expect(s.isOpen()).toBeTruthy() }) describe('reopening', () => { it('reopens if unexpectedly disconnected', async (done) => { - await s.open() + await s.connect() s.once('open', () => { expect(s.isOpen()).toBeTruthy() done() @@ -209,7 +202,7 @@ describe('SocketConnection', () => { }) it('errors if reopen fails', async (done) => { - await s.open() + await s.connect() s.options.url = 'badurl' s.once('error', (err) => { expect(err).toBeTruthy() @@ -222,7 +215,7 @@ describe('SocketConnection', () => { it('retries multiple times when disconnected', async (done) => { /* eslint-disable no-underscore-dangle */ - await s.open() + await s.connect() const goodUrl = s.options.url let retryCount = 0 s.options.url = 'badurl' @@ -244,7 +237,7 @@ describe('SocketConnection', () => { }) it('fails if exceed max retries', async (done) => { - await s.open() + await s.connect() s.options.maxRetries = 2 s.options.url = 'badurl' s.once('error', (err) => { @@ -255,14 +248,14 @@ describe('SocketConnection', () => { }) it('resets max retries on manual open after failure', async (done) => { - await s.open() + await s.connect() const goodUrl = s.options.url s.options.maxRetries = 2 s.options.url = 'badurl' s.once('error', async (err) => { expect(err).toBeTruthy() s.options.url = goodUrl - await s.open() + await s.connect() setTimeout(() => { expect(s.isReopening).toBeFalsy() expect(s.isOpen()).toBeTruthy() @@ -276,31 +269,31 @@ describe('SocketConnection', () => { const goodUrl = s.options.url s.options.url = 'badurl' await expect(async () => ( - s.open() + s.connect() )).rejects.toThrow('badurl') - await s.close() // shouldn't throw + await s.disconnect() // shouldn't throw expect(s.isClosed()).toBeTruthy() // ensure close await expect(async () => ( Promise.all([ - s.open(), - s.close(), + s.connect(), + s.disconnect(), ]) )).rejects.toThrow('badurl') s.options.url = goodUrl - await s.open() + await s.connect() expect(s.isOpen()).toBeTruthy() }) it('stops reopening if closed while reopening', async (done) => { - await s.open() + await s.connect() const goodUrl = s.options.url s.options.url = 'badurl' // once closed due to error, actually close s.once('close', async () => { // i.e. would reconnect if not closing s.options.url = goodUrl - await s.close() + await s.disconnect() // wait a moment setTimeout(() => { // ensure is closed, not reopening @@ -314,7 +307,7 @@ describe('SocketConnection', () => { }) it('stops reopening if closed while reopening, after some delay', async (done) => { - await s.open() + await s.connect() const goodUrl = s.options.url s.options.url = 'badurl' // once closed due to error, actually close @@ -323,7 +316,7 @@ describe('SocketConnection', () => { setTimeout(async () => { // i.e. would reconnect if not closing s.options.url = goodUrl - await s.close() + await s.disconnect() setTimeout(async () => { // ensure is closed, not reopening expect(s.isClosed()).toBeTruthy() @@ -339,7 +332,7 @@ describe('SocketConnection', () => { describe('send', () => { it('can send and receive messages', async (done) => { - await s.open() + await s.connect() s.once('message', ({ data } = {}) => { expect(data).toEqual('test') done() @@ -349,9 +342,9 @@ describe('SocketConnection', () => { }) it('waits for reopening if sending while reopening', async (done) => { - await s.open() - const open = s.open.bind(s) - s.open = async (...args) => { + await s.connect() + const open = s.connect.bind(s) + s.connect = async (...args) => { await wait(0) return open(...args) } @@ -366,7 +359,7 @@ describe('SocketConnection', () => { }) it('fails send if reopen fails', async () => { - await s.open() + await s.connect() // eslint-disable-next-line require-atomic-updates s.options.url = 'badurl' s.socket.close() @@ -376,8 +369,8 @@ describe('SocketConnection', () => { }) it('fails send if intentionally closed', async () => { - await s.open() - await s.close() + await s.connect() + await s.disconnect() await expect(async () => { await s.send('test') }).rejects.toThrow() From 2e41cff9dc0678f56c76cbdf65614eaafebc2ed1 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Sun, 23 Aug 2020 10:32:40 -0400 Subject: [PATCH 025/517] Tidy up Connection, s/open/connect. --- src/streams/SocketConnection.js | 57 ++++++++++++++++------------ test/unit/streams/Connection.test.js | 32 ++++++++-------- 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js index db73325f5..67f7d96b7 100644 --- a/src/streams/SocketConnection.js +++ b/src/streams/SocketConnection.js @@ -19,7 +19,7 @@ export default class SocketConnection extends EventEmitter { this.options.maxRetryWait = this.options.maxRetryWait != null ? this.options.maxRetryWait : 10000 this.shouldConnect = false this.retryCount = 1 - this.isReopening = false + this.isReconnecting = false const id = uniqueId('SocketConnection') /* istanbul ignore next */ if (options.debug) { @@ -56,47 +56,47 @@ export default class SocketConnection extends EventEmitter { } } - async reopen(...args) { - await this.reopenTask - this.reopenTask = (async () => { + async reconnect(...args) { + await this.reconnectTask + this.reconnectTask = (async () => { // closed, noop if (!this.shouldConnect) { - this.isReopening = false + this.isReconnecting = false return Promise.resolve() } - this.isReopening = true + this.isReconnecting = true // wait for a moment await this.backoffWait() // re-check if closed or closing if (!this.shouldConnect) { - this.isReopening = false + this.isReconnecting = false return Promise.resolve() } this.emit('retry') - this.debug('attempting to reopen %s of %s', this.retryCount, this.options.maxRetries) + this.debug('attempting to reconnect %s of %s', this.retryCount, this.options.maxRetries) return this._connect(...args).then((value) => { // reset retry state - this.reopenTask = undefined + this.reconnectTask = undefined this.retryCount = 1 - this.isReopening = false + this.isReconnecting = false return value }, (err) => { - this.debug('attempt to reopen %s of %s failed', this.retryCount, this.options.maxRetries, err) - this.reopenTask = undefined + this.debug('attempt to reconnect %s of %s failed', this.retryCount, this.options.maxRetries, err) + this.reconnectTask = undefined this.retryCount += 1 if (this.retryCount > this.options.maxRetries) { // no more retries - this.isReopening = false + this.isReconnecting = false throw err } // try again - return this.reopen() + return this.reconnect() }) })() - return this.reopenTask + return this.reconnectTask } async connect() { @@ -113,6 +113,8 @@ export default class SocketConnection extends EventEmitter { } let { socket } = this const isNew = !socket + + // create new socket if (!socket) { if (!this.options.url) { throw new Error('URL is not defined!') @@ -121,23 +123,27 @@ export default class SocketConnection extends EventEmitter { socket.binaryType = 'arraybuffer' this.socket = socket } - socket.addEventListener('close', () => { - if (this.shouldConnect) { - this.reopen().then(resolve).catch((error) => { - this.debug('failed reopening', error) - this.emit('error', error) - reject(error) - }) + if (!this.shouldConnect) { + return // expected close } + + // try reconnect on close if should be connected + this.reconnect().then(resolve).catch((error) => { + this.debug('failed reconnect', error) + this.emit('error', error) + reject(error) + }) }) socket.addEventListener('open', () => { - if (!this.shouldConnect) { - reject(new Error('disconnected before connected')) + if (this.shouldConnect) { + resolve() // expected open return } - resolve() + + // was disconnected while connecting + reject(new Error('disconnected before connected')) }) socket.addEventListener('error', (err) => { @@ -150,6 +156,7 @@ export default class SocketConnection extends EventEmitter { } if (isNew) { + /// convert WebSocket events to emitter events this.emit('opening') socket.addEventListener('message', (...args) => { if (this.socket !== socket) { return } diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js index eeb0e77ab..ddc12a179 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/streams/Connection.test.js @@ -73,7 +73,7 @@ describe('SocketConnection', () => { expect(onOpening).toHaveBeenCalledTimes(1) }) - it('can reopen after close', async () => { + it('can reconnect after close', async () => { await s.connect() expect(s.isOpen()).toBeTruthy() const oldSocket = s.socket @@ -191,8 +191,8 @@ describe('SocketConnection', () => { expect(s.isOpen()).toBeTruthy() }) - describe('reopening', () => { - it('reopens if unexpectedly disconnected', async (done) => { + describe('reconnecting', () => { + it('reconnects if unexpectedly disconnected', async (done) => { await s.connect() s.once('open', () => { expect(s.isOpen()).toBeTruthy() @@ -201,7 +201,7 @@ describe('SocketConnection', () => { s.socket.close() }) - it('errors if reopen fails', async (done) => { + it('errors if reconnect fails', async (done) => { await s.connect() s.options.url = 'badurl' s.once('error', (err) => { @@ -257,7 +257,7 @@ describe('SocketConnection', () => { s.options.url = goodUrl await s.connect() setTimeout(() => { - expect(s.isReopening).toBeFalsy() + expect(s.isReconnecting).toBeFalsy() expect(s.isOpen()).toBeTruthy() done() }) @@ -265,7 +265,7 @@ describe('SocketConnection', () => { s.socket.close() }) - it('can try reopen after error', async () => { + it('can try reconnect after error', async () => { const goodUrl = s.options.url s.options.url = 'badurl' await expect(async () => ( @@ -285,7 +285,7 @@ describe('SocketConnection', () => { expect(s.isOpen()).toBeTruthy() }) - it('stops reopening if closed while reopening', async (done) => { + it('stops reconnecting if closed while reconnecting', async (done) => { await s.connect() const goodUrl = s.options.url s.options.url = 'badurl' @@ -296,17 +296,17 @@ describe('SocketConnection', () => { await s.disconnect() // wait a moment setTimeout(() => { - // ensure is closed, not reopening + // ensure is closed, not reconnecting expect(s.isClosed()).toBeTruthy() - expect(s.isReopening).toBeFalsy() + expect(s.isReconnecting).toBeFalsy() done() }, 10) }) - // trigger reopening cycle + // trigger reconnecting cycle s.socket.close() }) - it('stops reopening if closed while reopening, after some delay', async (done) => { + it('stops reconnecting if closed while reconnecting, after some delay', async (done) => { await s.connect() const goodUrl = s.options.url s.options.url = 'badurl' @@ -318,14 +318,14 @@ describe('SocketConnection', () => { s.options.url = goodUrl await s.disconnect() setTimeout(async () => { - // ensure is closed, not reopening + // ensure is closed, not reconnecting expect(s.isClosed()).toBeTruthy() - expect(s.isReopening).toBeFalsy() + expect(s.isReconnecting).toBeFalsy() done() }, 20) }, 10) }) - // trigger reopening cycle + // trigger reconnecting cycle s.socket.close() }) }) @@ -341,7 +341,7 @@ describe('SocketConnection', () => { await s.send('test') }) - it('waits for reopening if sending while reopening', async (done) => { + it('waits for reconnecting if sending while reconnecting', async (done) => { await s.connect() const open = s.connect.bind(s) s.connect = async (...args) => { @@ -358,7 +358,7 @@ describe('SocketConnection', () => { await s.send('test') }) - it('fails send if reopen fails', async () => { + it('fails send if reconnect fails', async () => { await s.connect() // eslint-disable-next-line require-atomic-updates s.options.url = 'badurl' From 27ebbdda635011a85246703ff9029a7d463b3396 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 24 Aug 2020 13:44:26 -0400 Subject: [PATCH 026/517] Tune reconnection, finish s/open/connect. --- src/streams/SocketConnection.js | 38 +++-- test/unit/streams/Connection.test.js | 243 ++++++++++++++++----------- 2 files changed, 162 insertions(+), 119 deletions(-) diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js index 67f7d96b7..59643a489 100644 --- a/src/streams/SocketConnection.js +++ b/src/streams/SocketConnection.js @@ -19,7 +19,7 @@ export default class SocketConnection extends EventEmitter { this.options.maxRetryWait = this.options.maxRetryWait != null ? this.options.maxRetryWait : 10000 this.shouldConnect = false this.retryCount = 1 - this.isReconnecting = false + this._isReconnecting = false const id = uniqueId('SocketConnection') /* istanbul ignore next */ if (options.debug) { @@ -61,27 +61,24 @@ export default class SocketConnection extends EventEmitter { this.reconnectTask = (async () => { // closed, noop if (!this.shouldConnect) { - this.isReconnecting = false + this._isReconnecting = false return Promise.resolve() } - this.isReconnecting = true + this._isReconnecting = true // wait for a moment await this.backoffWait() // re-check if closed or closing if (!this.shouldConnect) { - this.isReconnecting = false + this._isReconnecting = false return Promise.resolve() } - this.emit('retry') - this.debug('attempting to reconnect %s of %s', this.retryCount, this.options.maxRetries) - return this._connect(...args).then((value) => { // reset retry state this.reconnectTask = undefined this.retryCount = 1 - this.isReconnecting = false + this._isReconnecting = false return value }, (err) => { this.debug('attempt to reconnect %s of %s failed', this.retryCount, this.options.maxRetries, err) @@ -89,10 +86,12 @@ export default class SocketConnection extends EventEmitter { this.retryCount += 1 if (this.retryCount > this.options.maxRetries) { // no more retries - this.isReconnecting = false + this._isReconnecting = false throw err } // try again + this.debug('attempting to reconnect %s of %s', this.retryCount, this.options.maxRetries) + this.emit('reconnecting') return this.reconnect() }) })() @@ -157,19 +156,19 @@ export default class SocketConnection extends EventEmitter { if (isNew) { /// convert WebSocket events to emitter events - this.emit('opening') + this.emit('connecting') socket.addEventListener('message', (...args) => { if (this.socket !== socket) { return } this.emit('message', ...args) }) socket.addEventListener('open', (event) => { if (this.socket !== socket) { return } - this.emit('open', event) + this.emit('connected', event) }) socket.addEventListener('close', (event) => { if (this.socket === socket) { this.socket = undefined - this.emit('close', event) + this.emit('disconnected', event) } }) socket.addEventListener('error', (err) => { @@ -221,9 +220,8 @@ export default class SocketConnection extends EventEmitter { resolve() }) - this.emit('closing') - if (socket.readyState === WebSocket.OPEN) { + this.emit('disconnecting') socket.close() } } catch (err) { @@ -258,7 +256,11 @@ export default class SocketConnection extends EventEmitter { }) } - isOpen() { + isReconnecting() { + return this._isReconnecting + } + + isConnected() { if (!this.socket) { return false } @@ -266,7 +268,7 @@ export default class SocketConnection extends EventEmitter { return this.socket.readyState === WebSocket.OPEN } - isClosed() { + isDisconnected() { if (!this.socket) { return true } @@ -274,14 +276,14 @@ export default class SocketConnection extends EventEmitter { return this.socket.readyState === WebSocket.CLOSED } - isClosing() { + isDisconnecting() { if (!this.socket) { return false } return this.socket.readyState === WebSocket.CLOSING } - isOpening() { + isConnecting() { if (!this.socket) { return false } diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js index ddc12a179..b06e045a0 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/streams/Connection.test.js @@ -6,9 +6,11 @@ import SocketConnection from '../../../src/streams/SocketConnection' describe('SocketConnection', () => { let s - let onOpen - let onOpening - let onClose + let onConnected + let onConnecting + let onDisconnecting + let onDisconnected + let onReconnecting let onError let onMessage @@ -18,12 +20,16 @@ describe('SocketConnection', () => { maxRetries: 5 }) - onOpen = jest.fn() - s.on('open', onOpen) - onOpening = jest.fn() - s.on('opening', onOpening) - onClose = jest.fn() - s.on('close', onClose) + onConnected = jest.fn() + s.on('connected', onConnected) + onConnecting = jest.fn() + s.on('connecting', onConnecting) + onDisconnected = jest.fn() + s.on('disconnected', onDisconnected) + onDisconnecting = jest.fn() + s.on('disconnecting', onDisconnecting) + onReconnecting = jest.fn() + s.on('reconnecting', onReconnecting) onError = jest.fn() s.on('error', onError) onMessage = jest.fn() @@ -34,56 +40,91 @@ describe('SocketConnection', () => { await s.disconnect() }) - it('can open & close', async () => { - const openTask = s.connect() - expect(s.isOpening()).toBeTruthy() - await openTask - expect(s.isClosed()).toBeFalsy() - expect(s.isClosing()).toBeFalsy() - expect(s.isOpening()).toBeFalsy() - expect(s.isOpen()).toBeTruthy() - const closeTask = s.disconnect() - expect(s.isClosing()).toBeTruthy() - await closeTask - expect(s.isOpen()).toBeFalsy() - expect(s.isClosing()).toBeFalsy() - expect(s.isOpening()).toBeFalsy() - expect(s.isClosed()).toBeTruthy() + it('can connect & disconnect', async () => { + const connectTask = s.connect() + expect(s.isConnecting()).toBeTruthy() + await connectTask + expect(s.isDisconnected()).toBeFalsy() + expect(s.isDisconnecting()).toBeFalsy() + expect(s.isConnecting()).toBeFalsy() + expect(s.isConnected()).toBeTruthy() + const disconnectTask = s.disconnect() + expect(s.isDisconnecting()).toBeTruthy() + await disconnectTask + expect(s.isConnected()).toBeFalsy() + expect(s.isDisconnecting()).toBeFalsy() + expect(s.isConnecting()).toBeFalsy() + expect(s.isDisconnected()).toBeTruthy() // check events - expect(onOpen).toHaveBeenCalledTimes(1) - expect(onClose).toHaveBeenCalledTimes(1) + expect(onConnected).toHaveBeenCalledTimes(1) + expect(onDisconnected).toHaveBeenCalledTimes(1) }) - it('can open after already open', async () => { + it('can connect after already connected', async () => { await s.connect() await s.connect() - expect(s.isOpen()).toBeTruthy() - // only one open event should fire - expect(onOpen).toHaveBeenCalledTimes(1) + expect(s.isConnected()).toBeTruthy() + // only one connect event should fire + expect(onConnected).toHaveBeenCalledTimes(1) }) - it('can open twice in same tick', async () => { + it('can connect twice in same tick', async () => { await Promise.all([ s.connect(), s.connect(), ]) - expect(s.isOpen()).toBeTruthy() - // only one open event should fire - expect(onOpen).toHaveBeenCalledTimes(1) - expect(onOpening).toHaveBeenCalledTimes(1) + expect(s.isConnected()).toBeTruthy() + // only one connect event should fire + expect(onConnected).toHaveBeenCalledTimes(1) + expect(onConnecting).toHaveBeenCalledTimes(1) }) - it('can reconnect after close', async () => { + it('fires all events once if connected twice in same tick', async () => { + await Promise.all([ + s.connect(), + s.connect(), + ]) + expect(s.isConnected()).toBeTruthy() + await Promise.all([ + s.disconnect(), + s.disconnect(), + ]) + expect(s.isDisconnected()).toBeTruthy() + // only one connect event should fire + expect(onConnected).toHaveBeenCalledTimes(1) + expect(onDisconnected).toHaveBeenCalledTimes(1) + expect(onDisconnecting).toHaveBeenCalledTimes(1) + expect(onConnecting).toHaveBeenCalledTimes(1) + }) + + it('fires all events minimally if connected twice in same tick then reconnected', async () => { + await Promise.all([ + s.connect(), + s.connect(), + ]) + s.socket.close() + await s.connect() + + expect(s.isConnected()).toBeTruthy() + + // only one connect event should fire + expect(onConnected).toHaveBeenCalledTimes(2) + expect(onDisconnected).toHaveBeenCalledTimes(1) + expect(onDisconnecting).toHaveBeenCalledTimes(0) + expect(onConnecting).toHaveBeenCalledTimes(2) + }) + + it('can connect again after disconnect', async () => { await s.connect() - expect(s.isOpen()).toBeTruthy() + expect(s.isConnected()).toBeTruthy() const oldSocket = s.socket await s.disconnect() - expect(s.isClosed()).toBeTruthy() + expect(s.isDisconnected()).toBeTruthy() await s.connect() - expect(s.isOpen()).toBeTruthy() + expect(s.isConnected()).toBeTruthy() // check events - expect(onOpen).toHaveBeenCalledTimes(2) - expect(onClose).toHaveBeenCalledTimes(1) + expect(onConnected).toHaveBeenCalledTimes(2) + expect(onDisconnected).toHaveBeenCalledTimes(1) // ensure new socket expect(s.socket).not.toBe(oldSocket) }) @@ -92,78 +133,78 @@ describe('SocketConnection', () => { s = new SocketConnection({ url: undefined, }) - onOpen = jest.fn() - s.on('open', onOpen) + onConnected = jest.fn() + s.on('connected', onConnected) await expect(async () => { await s.connect() }).rejects.toThrow('not defined') - expect(onOpen).toHaveBeenCalledTimes(0) + expect(onConnected).toHaveBeenCalledTimes(0) }) it('rejects if bad url', async () => { s = new SocketConnection({ url: 'badurl' }) - onOpen = jest.fn() - s.on('open', onOpen) + onConnected = jest.fn() + s.on('connected', onConnected) await expect(async () => { await s.connect() }).rejects.toThrow('badurl') - expect(onOpen).toHaveBeenCalledTimes(0) + expect(onConnected).toHaveBeenCalledTimes(0) }) it('rejects if cannot connect', async () => { s = new SocketConnection({ url: 'wss://streamr.network/nope' }) - onOpen = jest.fn() - s.on('open', onOpen) + onConnected = jest.fn() + s.on('connected', onConnected) await expect(async () => { await s.connect() }).rejects.toThrow('Unexpected server response') - expect(onOpen).toHaveBeenCalledTimes(0) + expect(onConnected).toHaveBeenCalledTimes(0) }) - it('close does not error if never opened', async () => { - expect(s.isClosed()).toBeTruthy() + it('disconnect does not error if never connected', async () => { + expect(s.isDisconnected()).toBeTruthy() await s.disconnect() - expect(s.isClosed()).toBeTruthy() - expect(onClose).toHaveBeenCalledTimes(0) + expect(s.isDisconnected()).toBeTruthy() + expect(onDisconnected).toHaveBeenCalledTimes(0) }) - it('close does not error if already closed', async () => { + it('disconnect does not error if already disconnected', async () => { await s.connect() await s.disconnect() - expect(s.isClosed()).toBeTruthy() + expect(s.isDisconnected()).toBeTruthy() await s.disconnect() - expect(s.isClosed()).toBeTruthy() - expect(onClose).toHaveBeenCalledTimes(1) + expect(s.isDisconnected()).toBeTruthy() + expect(onDisconnected).toHaveBeenCalledTimes(1) }) - it('close does not error if already closing', async () => { + it('disconnect does not error if already closing', async () => { await s.connect() await Promise.all([ s.disconnect(), s.disconnect(), ]) - expect(s.isClosed()).toBeTruthy() - expect(onOpen).toHaveBeenCalledTimes(1) - expect(onClose).toHaveBeenCalledTimes(1) + expect(s.isDisconnected()).toBeTruthy() + expect(onConnected).toHaveBeenCalledTimes(1) + expect(onDisconnected).toHaveBeenCalledTimes(1) }) - it('can handle close before open complete', async () => { + it('can handle disconnect before connect complete', async () => { await Promise.all([ expect(async () => ( s.connect() )).rejects.toThrow(), s.disconnect() ]) - expect(s.isClosed()).toBeTruthy() - expect(onOpen).toHaveBeenCalledTimes(1) - expect(onClose).toHaveBeenCalledTimes(1) + expect(s.isDisconnected()).toBeTruthy() + expect(onConnected).toHaveBeenCalledTimes(1) + expect(onDisconnected).toHaveBeenCalledTimes(1) }) - it('can handle open before close complete', async () => { + it('can handle connect before disconnect complete', async () => { await s.connect() await Promise.all([ expect(async () => ( @@ -171,31 +212,31 @@ describe('SocketConnection', () => { )).rejects.toThrow(), s.connect() ]) - expect(s.isOpen()).toBeTruthy() - expect(onOpen).toHaveBeenCalledTimes(2) - expect(onClose).toHaveBeenCalledTimes(1) + expect(s.isConnected()).toBeTruthy() + expect(onConnected).toHaveBeenCalledTimes(2) + expect(onDisconnected).toHaveBeenCalledTimes(1) }) - it('emits error but does not close if open event handler fails', async (done) => { + it('emits error but does not disconnect if connect event handler fails', async (done) => { const error = new Error('expected error') - s.once('open', () => { + s.once('connected', () => { throw error }) s.once('error', async (err) => { expect(err).toBe(error) await wait() - expect(s.isOpen()).toBeTruthy() + expect(s.isConnected()).toBeTruthy() done() }) await s.connect() - expect(s.isOpen()).toBeTruthy() + expect(s.isConnected()).toBeTruthy() }) describe('reconnecting', () => { it('reconnects if unexpectedly disconnected', async (done) => { await s.connect() - s.once('open', () => { - expect(s.isOpen()).toBeTruthy() + s.once('connected', () => { + expect(s.isConnected()).toBeTruthy() done() }) s.socket.close() @@ -206,8 +247,8 @@ describe('SocketConnection', () => { s.options.url = 'badurl' s.once('error', (err) => { expect(err).toBeTruthy() - expect(onOpen).toHaveBeenCalledTimes(1) - expect(s.isClosed()).toBeTruthy() + expect(onConnected).toHaveBeenCalledTimes(1) + expect(s.isDisconnected()).toBeTruthy() done() }) s.socket.close() @@ -219,7 +260,7 @@ describe('SocketConnection', () => { const goodUrl = s.options.url let retryCount = 0 s.options.url = 'badurl' - s.on('retry', () => { + s.on('reconnecting', () => { retryCount += 1 // fail first 3 tries // pass after @@ -227,8 +268,8 @@ describe('SocketConnection', () => { s.options.url = goodUrl } }) - s.once('open', () => { - expect(s.isOpen()).toBeTruthy() + s.once('connected', () => { + expect(s.isConnected()).toBeTruthy() expect(retryCount).toEqual(3) done() }) @@ -247,7 +288,7 @@ describe('SocketConnection', () => { s.socket.close() }) - it('resets max retries on manual open after failure', async (done) => { + it('resets max retries on manual connect after failure', async (done) => { await s.connect() const goodUrl = s.options.url s.options.maxRetries = 2 @@ -257,8 +298,8 @@ describe('SocketConnection', () => { s.options.url = goodUrl await s.connect() setTimeout(() => { - expect(s.isReconnecting).toBeFalsy() - expect(s.isOpen()).toBeTruthy() + expect(s.isReconnecting()).toBeFalsy() + expect(s.isConnected()).toBeTruthy() done() }) }) @@ -272,7 +313,7 @@ describe('SocketConnection', () => { s.connect() )).rejects.toThrow('badurl') await s.disconnect() // shouldn't throw - expect(s.isClosed()).toBeTruthy() + expect(s.isDisconnected()).toBeTruthy() // ensure close await expect(async () => ( Promise.all([ @@ -282,23 +323,23 @@ describe('SocketConnection', () => { )).rejects.toThrow('badurl') s.options.url = goodUrl await s.connect() - expect(s.isOpen()).toBeTruthy() + expect(s.isConnected()).toBeTruthy() }) - it('stops reconnecting if closed while reconnecting', async (done) => { + it('stops reconnecting if disconnected while reconnecting', async (done) => { await s.connect() const goodUrl = s.options.url s.options.url = 'badurl' - // once closed due to error, actually close - s.once('close', async () => { + // once disconnected due to error, actually close + s.once('disconnected', async () => { // i.e. would reconnect if not closing s.options.url = goodUrl await s.disconnect() // wait a moment setTimeout(() => { - // ensure is closed, not reconnecting - expect(s.isClosed()).toBeTruthy() - expect(s.isReconnecting).toBeFalsy() + // ensure is disconnected, not reconnecting + expect(s.isDisconnected()).toBeTruthy() + expect(s.isReconnecting()).toBeFalsy() done() }, 10) }) @@ -306,21 +347,21 @@ describe('SocketConnection', () => { s.socket.close() }) - it('stops reconnecting if closed while reconnecting, after some delay', async (done) => { + it('stops reconnecting if disconnected while reconnecting, after some delay', async (done) => { await s.connect() const goodUrl = s.options.url s.options.url = 'badurl' - // once closed due to error, actually close - s.once('close', async () => { + // once disconnected due to error, actually close + s.once('disconnected', async () => { // wait a moment setTimeout(async () => { // i.e. would reconnect if not closing s.options.url = goodUrl await s.disconnect() setTimeout(async () => { - // ensure is closed, not reconnecting - expect(s.isClosed()).toBeTruthy() - expect(s.isReconnecting).toBeFalsy() + // ensure is disconnected, not reconnecting + expect(s.isDisconnected()).toBeTruthy() + expect(s.isReconnecting()).toBeFalsy() done() }, 20) }, 10) @@ -343,10 +384,10 @@ describe('SocketConnection', () => { it('waits for reconnecting if sending while reconnecting', async (done) => { await s.connect() - const open = s.connect.bind(s) + const connect = s.connect.bind(s) s.connect = async (...args) => { await wait(0) - return open(...args) + return connect(...args) } s.once('message', ({ data } = {}) => { @@ -368,7 +409,7 @@ describe('SocketConnection', () => { }).rejects.toThrow('badurl') }) - it('fails send if intentionally closed', async () => { + it('fails send if intentionally disconnected', async () => { await s.connect() await s.disconnect() await expect(async () => { From 4c43b4a08a2e7fd6836327b3bbb16eecf9043aea Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 25 Aug 2020 14:23:08 -0400 Subject: [PATCH 027/517] Move autoconnect logic into connection. --- src/streams/SocketConnection.js | 9 ++++++--- test/unit/streams/Connection.test.js | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js index 59643a489..9eb0f1ffd 100644 --- a/src/streams/SocketConnection.js +++ b/src/streams/SocketConnection.js @@ -14,6 +14,7 @@ export default class SocketConnection extends EventEmitter { constructor(options) { super() this.options = options + this.options.autoConnect = !!this.options.autoConnect this.options.maxRetries = this.options.maxRetries != null ? this.options.maxRetries : 10 this.options.retryBackoffFactor = this.options.retryBackoffFactor != null ? this.options.retryBackoffFactor : 1.2 this.options.maxRetryWait = this.options.maxRetryWait != null ? this.options.maxRetryWait : 10000 @@ -231,13 +232,15 @@ export default class SocketConnection extends EventEmitter { } async send(msg) { + if (this.options.autoConnect || this.shouldConnect) { + // should be open, so wait for open or trigger new open + await this.connect() + } + if (!this.shouldConnect || !this.socket) { throw new Error('connection closed or closing') } - // should be open, so wait for open or trigger new open - await this.connect() - return new Promise((resolve, reject) => { // promisify send this.socket.send(msg, (err) => { diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js index b06e045a0..2d9d18631 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/streams/Connection.test.js @@ -382,6 +382,32 @@ describe('SocketConnection', () => { await s.send('test') }) + it('fails if not autoconnecting or manually connected', async () => { + await expect(async () => { + await s.send('test') + }).rejects.toThrow('connection') + }) + + it('waits for connection if sending while connecting', async (done) => { + s.once('message', ({ data } = {}) => { + expect(data).toEqual('test') + done() + }) + + s.connect() // no await + await s.send('test') + }) + + it('creates connection and waits if autoconnect true', async (done) => { + s.options.autoConnect = true + s.once('message', ({ data } = {}) => { + expect(data).toEqual('test') + done() + }) + // no connect + await s.send('test') + }) + it('waits for reconnecting if sending while reconnecting', async (done) => { await s.connect() const connect = s.connect.bind(s) From 695cbd1da63519dfa0e33ca78e463f605a125108 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 1 Sep 2020 16:29:41 -0400 Subject: [PATCH 028/517] Isolate open/close functions. Tighten reconnect. --- src/streams/SocketConnection.js | 398 +++++++++++++++++-------- test/unit/streams/Connection.test.js | 419 ++++++++++++++++----------- 2 files changed, 528 insertions(+), 289 deletions(-) diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js index 9eb0f1ffd..747359f5e 100644 --- a/src/streams/SocketConnection.js +++ b/src/streams/SocketConnection.js @@ -1,8 +1,69 @@ +import { PassThrough, Writable, pipeline } from 'stream' + import EventEmitter from 'eventemitter3' import debugFactory from 'debug' import uniqueId from 'lodash.uniqueid' import WebSocket from 'ws' +async function OpenWebSocket(url, ...args) { + return new Promise((resolve, reject) => { + try { + if (!url) { + throw new Error('URL is not defined!') + } + const socket = new WebSocket(url, ...args) + socket.binaryType = 'arraybuffer' + socket.onopen = () => resolve(socket) + let error + socket.onclose = () => { + if (error) { + reject(error) + return + } + reject(new Error('socket closed')) + } + socket.onerror = (event) => { + error = event.error || new Error(event) + } + return + } catch (err) { + reject(err) + } + }) +} + +async function CloseWebSocket(socket) { + return new Promise((resolve, reject) => { + if (!socket || socket.readyState === WebSocket.CLOSED) { + resolve() + return + } + + const waitThenClose = () => ( + resolve(CloseWebSocket(socket)) + ) + + if (socket.readyState === WebSocket.OPENING) { + socket.addEventListener('error', waitThenClose) + socket.addEventListener('open', waitThenClose) + } + + if (socket.readyState === WebSocket.OPEN) { + socket.addEventListener('close', resolve) + try { + socket.close() + } catch (err) { + reject(err) + return + } + } + + if (socket.readyState === WebSocket.CLOSING) { + socket.addEventListener('close', resolve) + } + }) +} + /** * Wraps WebSocket open/close with promise methods * adds events @@ -24,28 +85,33 @@ export default class SocketConnection extends EventEmitter { const id = uniqueId('SocketConnection') /* istanbul ignore next */ if (options.debug) { - this.debug = options.debug.extend(id) + this._debug = options.debug.extend(id) } else { - this.debug = debugFactory(`StreamrClient::${id}`) + this._debug = debugFactory(`StreamrClient::${id}`) } + this.debug = this._debug } async backoffWait() { return new Promise((resolve) => { + clearTimeout(this._backoffTimeout) const timeout = Math.min( this.options.maxRetryWait, // max wait time Math.round((this.retryCount * 10) ** this.options.retryBackoffFactor) ) this.debug('waiting %sms', timeout) - setTimeout(resolve, timeout) + this._backoffTimeout = setTimeout(resolve, timeout) }) } emit(event, ...args) { if (event === 'error') { + this.debug('emit', event, args) return super.emit(event, ...args) } + this.debug('emit', event) + // note if event handler is async and it rejects we're kinda hosed // until node lands unhandledrejection support // in eventemitter @@ -58,14 +124,25 @@ export default class SocketConnection extends EventEmitter { } async reconnect(...args) { - await this.reconnectTask - this.reconnectTask = (async () => { + if (this.reconnectTask) { + return this.reconnectTask + } + + const reconnectTask = (async () => { + if (this.retryCount > this.options.maxRetries) { + // no more retries + this._isReconnecting = false + return Promise.resolve() + } + // closed, noop if (!this.shouldConnect) { this._isReconnecting = false return Promise.resolve() } + this._isReconnecting = true + this.debug('reconnect()') // wait for a moment await this.backoffWait() @@ -75,173 +152,263 @@ export default class SocketConnection extends EventEmitter { return Promise.resolve() } - return this._connect(...args).then((value) => { + if (this.isConnected()) { + this._isReconnecting = false + return Promise.resolve() + } + + const { retryCount } = this + const { maxRetries } = this.options + // try again + this.debug('attempting to reconnect %s of %s', retryCount, maxRetries) + this.emit('reconnecting') + return this._connectOnce(...args).then((value) => { + this.debug('reconnect %s of %s successful', retryCount, maxRetries) // reset retry state this.reconnectTask = undefined - this.retryCount = 1 this._isReconnecting = false + this.retryCount = 1 return value }, (err) => { - this.debug('attempt to reconnect %s of %s failed', this.retryCount, this.options.maxRetries, err) + this.debug('attempt to reconnect %s of %s failed', retryCount, maxRetries, err) + this.debug = this._debug this.reconnectTask = undefined this.retryCount += 1 if (this.retryCount > this.options.maxRetries) { + this.debug('no more retries') // no more retries this._isReconnecting = false throw err } - // try again - this.debug('attempting to reconnect %s of %s', this.retryCount, this.options.maxRetries) - this.emit('reconnecting') + this.debug('trying again') return this.reconnect() }) - })() + })().finally(() => { + if (this.reconnectTask === reconnectTask) { + this.reconnectTask = undefined + } + }) + this.reconnectTask = reconnectTask return this.reconnectTask } async connect() { this.shouldConnect = true - return this._connect() - } + if (this.initialConnectTask) { + return this.initialConnectTask + } + const initialConnectTask = this._connectOnce() + .catch((err) => { + if (this.initialConnectTask === initialConnectTask) { + this.initialConnectTask = undefined + } - async _connect() { - return new Promise((resolve, reject) => { - try { + // reconnect on initial connection failure if (!this.shouldConnect) { - reject(new Error('disconnected before connected')) - return + throw err } - let { socket } = this - const isNew = !socket - - // create new socket - if (!socket) { - if (!this.options.url) { - throw new Error('URL is not defined!') - } - socket = new WebSocket(this.options.url) - socket.binaryType = 'arraybuffer' - this.socket = socket + + this.debug('error while opening', err) + this.debug = this._debug + if (this.initialConnectTask === initialConnectTask) { + this.initialConnectTask = undefined } - socket.addEventListener('close', () => { - if (!this.shouldConnect) { - return // expected close - } - - // try reconnect on close if should be connected - this.reconnect().then(resolve).catch((error) => { - this.debug('failed reconnect', error) - this.emit('error', error) - reject(error) - }) + + // eslint-disable-next-line promise/no-nesting + return this.reconnect().catch((error) => { + this.debug('failed reconnect during initial connection', error) + throw error }) + }).catch((error) => { + if (!this._isReconnecting) { + this.emit('error', error) + } + throw error + }) + .finally(() => { + if (this.initialConnectTask === initialConnectTask) { + this.initialConnectTask = undefined + } + }) + this.initialConnectTask = initialConnectTask + return this.initialConnectTask + } + + async reconnectOnUnexpectedClose() { + if (!this.shouldConnect) { + return + } - socket.addEventListener('open', () => { - if (this.shouldConnect) { - resolve() // expected open - return - } + this.debug('unexpected close') + await this.reconnect().catch((error) => { + this.debug('failed reconnect after connected', error) + this.emit('error', error) + }) + } - // was disconnected while connecting - reject(new Error('disconnected before connected')) - }) + async _connectOnce() { + const { debug } = this + if (!this.shouldConnect) { + throw new Error('disconnected before connected') + } - socket.addEventListener('error', (err) => { - const error = err.error || new Error(err) - reject(error) - }) + if (this.isConnected()) { + debug('connect(): aleady connected') + return Promise.resolve() + } - if (socket.readyState === WebSocket.OPEN) { - resolve() - } + if (this.socket && this.socket.readyState === WebSocket.CLOSING) { + debug('waiting for close') + await CloseWebSocket(this.socket) + } - if (isNew) { - /// convert WebSocket events to emitter events - this.emit('connecting') - socket.addEventListener('message', (...args) => { - if (this.socket !== socket) { return } - this.emit('message', ...args) - }) - socket.addEventListener('open', (event) => { - if (this.socket !== socket) { return } - this.emit('connected', event) - }) - socket.addEventListener('close', (event) => { - if (this.socket === socket) { - this.socket = undefined - this.emit('disconnected', event) - } - }) - socket.addEventListener('error', (err) => { - if (this.socket !== socket) { return } - const error = err.error || new Error(err) - this.emit('error', error) - }) - } - } catch (err) { - reject(err) + if (this.connectTask) { + debug('reuse connection %s', this.socket && this.socket.readyState) + return this.connectTask + } + + debug('connect()', this.socket && this.socket.readyState) + const connectTask = this._connect().then((value) => { + // reconnect on unexpected failure + this.socket.addEventListener('close', () => this.reconnectOnUnexpectedClose()) + return value + }).finally(() => { + this.connectTask = undefined + }) + + this.connectTask = connectTask + return this.connectTask + } + + async _connect() { + this.debug = this._debug.extend(uniqueId('socket')) + const { debug } = this + this.emit('connecting') + const socket = await OpenWebSocket(this.options.url) + debug('connected') + this.socket = socket + if (!this.shouldConnect) { + // was disconnected while connecting + throw new Error('disconnected before connected') + } + + socket.addEventListener('message', (messageEvent, ...args) => { + if (this.socket !== socket) { return } + debug('<< %s', messageEvent && messageEvent.data) + this.emit('message', messageEvent, ...args) + }) + + socket.addEventListener('close', (event) => { + debug('closed') + if (this.socket === socket) { + this.socket = undefined + this.emit('disconnected', event) + this.debug = this._debug + } + }) + + socket.addEventListener('error', (err) => { + debug('error', this.socket !== socket, err) + if (this.socket !== socket) { return } + const error = err.error || new Error(err) + if (!this._isReconnecting) { + this.emit('error', error) } }) + + this.emit('connected') } async disconnect() { + this.debug('disconnect()') this.shouldConnect = false - return this._disconnect() + if (this.disconnectTask) { + await this.disconnectTask + } + const disconnectTask = this._disconnect() + .catch((err) => { + this.emit('error', err) + throw err + }).finally(() => { + if (this.disconnectTask === disconnectTask) { + this.disconnectTask = undefined + } + }) + this.disconnectTask = disconnectTask + return this.disconnectTask } async _disconnect() { - return new Promise((resolve, reject) => { + if (this.connectTask) { try { - if (this.shouldConnect) { - reject(new Error('connected before disconnected')) - return - } + await this.connectTask + } catch (err) { + // ignore + } + } - const { socket } = this - if (!socket || socket.readyState === WebSocket.CLOSED) { - resolve() - return - } + if (this.shouldConnect) { + throw new Error('connected before disconnected') + } - socket.addEventListener('open', () => { - if (!this.shouldConnect) { - resolve(this._disconnect()) - } - }) + if (this.isConnected()) { + this.emit('disconnecting') + } - socket.addEventListener('error', (err) => { - const error = err.error || new Error(err) - reject(error) - }) - socket.addEventListener('close', () => { - if (this.shouldConnect) { - reject(new Error('connected before disconnected')) - return - } - resolve() - }) + await CloseWebSocket(this.socket) - if (socket.readyState === WebSocket.OPEN) { - this.emit('disconnecting') - socket.close() - } - } catch (err) { + if (this.shouldConnect) { + throw new Error('connected before disconnected') + } + } + + async nextConnection() { + if (this.isConnected()) { + return Promise.resolve() + } + + return new Promise((resolve, reject) => { + let onError + const onConnected = () => { + this.off('error', onError) + resolve() + } + onError = (err) => { + this.off('connected', onConnected) reject(err) } + this.once('connected', onConnected) + this.once('error', onError) }) } - async send(msg) { + async triggerConnectionOrWait() { + return Promise.all([ + this.nextConnection(), + this.maybeConnect() + ]) + } + + async maybeConnect() { + this.debug('maybeConnect', this.options.autoConnect || this.shouldConnect) if (this.options.autoConnect || this.shouldConnect) { // should be open, so wait for open or trigger new open await this.connect() } + } - if (!this.shouldConnect || !this.socket) { + async send(msg) { + this.debug('send()') + await this.maybeConnect() + if (!this.isConnected()) { + // note we can't just let socket.send fail, + // have to do this check ourselves because the error appears + // to be uncatchable in the browser throw new Error('connection closed or closing') } return new Promise((resolve, reject) => { + this.debug('>> %s', msg) // promisify send this.socket.send(msg, (err) => { /* istanbul ignore next */ @@ -288,6 +455,7 @@ export default class SocketConnection extends EventEmitter { isConnecting() { if (!this.socket) { + if (this.connectTask) { return true } return false } return this.socket.readyState === WebSocket.CONNECTING diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js index 2d9d18631..11a54e9f8 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/streams/Connection.test.js @@ -14,10 +14,12 @@ describe('SocketConnection', () => { let onError let onMessage + let expectErrors = 0 // check no errors by default + beforeEach(() => { s = new SocketConnection({ url: 'wss://echo.websocket.org/', - maxRetries: 5 + maxRetries: 3 }) onConnected = jest.fn() @@ -34,202 +36,270 @@ describe('SocketConnection', () => { s.on('error', onError) onMessage = jest.fn() s.on('message', onMessage) + expectErrors = 0 }) afterEach(async () => { - await s.disconnect() + await wait() + // ensure no unexpected errors + expect(onError).toHaveBeenCalledTimes(expectErrors) }) - it('can connect & disconnect', async () => { - const connectTask = s.connect() - expect(s.isConnecting()).toBeTruthy() - await connectTask - expect(s.isDisconnected()).toBeFalsy() - expect(s.isDisconnecting()).toBeFalsy() - expect(s.isConnecting()).toBeFalsy() - expect(s.isConnected()).toBeTruthy() - const disconnectTask = s.disconnect() - expect(s.isDisconnecting()).toBeTruthy() - await disconnectTask - expect(s.isConnected()).toBeFalsy() - expect(s.isDisconnecting()).toBeFalsy() - expect(s.isConnecting()).toBeFalsy() - expect(s.isDisconnected()).toBeTruthy() - // check events - expect(onConnected).toHaveBeenCalledTimes(1) - expect(onDisconnected).toHaveBeenCalledTimes(1) + afterEach(async () => { + await s.disconnect() }) - it('can connect after already connected', async () => { - await s.connect() - await s.connect() - expect(s.isConnected()).toBeTruthy() - // only one connect event should fire - expect(onConnected).toHaveBeenCalledTimes(1) - }) + describe('basics', () => { + it('can connect & disconnect', async () => { + const connectTask = s.connect() + expect(s.isConnecting()).toBeTruthy() + await connectTask + expect(s.isDisconnected()).toBeFalsy() + expect(s.isDisconnecting()).toBeFalsy() + expect(s.isConnecting()).toBeFalsy() + expect(s.isConnected()).toBeTruthy() + const disconnectTask = s.disconnect() + expect(s.isDisconnecting()).toBeTruthy() + await disconnectTask + expect(s.isConnected()).toBeFalsy() + expect(s.isDisconnecting()).toBeFalsy() + expect(s.isConnecting()).toBeFalsy() + expect(s.isDisconnected()).toBeTruthy() + // check events + expect(onConnected).toHaveBeenCalledTimes(1) + expect(onDisconnected).toHaveBeenCalledTimes(1) + }) - it('can connect twice in same tick', async () => { - await Promise.all([ - s.connect(), - s.connect(), - ]) - expect(s.isConnected()).toBeTruthy() - // only one connect event should fire - expect(onConnected).toHaveBeenCalledTimes(1) - expect(onConnecting).toHaveBeenCalledTimes(1) - }) + it('can connect after already connected', async () => { + await s.connect() + await s.connect() + expect(s.isConnected()).toBeTruthy() + // only one connect event should fire + expect(onConnected).toHaveBeenCalledTimes(1) + expect(onConnecting).toHaveBeenCalledTimes(1) + }) - it('fires all events once if connected twice in same tick', async () => { - await Promise.all([ - s.connect(), - s.connect(), - ]) - expect(s.isConnected()).toBeTruthy() - await Promise.all([ - s.disconnect(), - s.disconnect(), - ]) - expect(s.isDisconnected()).toBeTruthy() - // only one connect event should fire - expect(onConnected).toHaveBeenCalledTimes(1) - expect(onDisconnected).toHaveBeenCalledTimes(1) - expect(onDisconnecting).toHaveBeenCalledTimes(1) - expect(onConnecting).toHaveBeenCalledTimes(1) - }) + it('can connect twice in same tick', async () => { + await Promise.all([ + s.connect(), + s.connect(), + ]) + expect(s.isConnected()).toBeTruthy() + // only one connect event should fire + expect(onConnected).toHaveBeenCalledTimes(1) + expect(onConnecting).toHaveBeenCalledTimes(1) + }) - it('fires all events minimally if connected twice in same tick then reconnected', async () => { - await Promise.all([ - s.connect(), - s.connect(), - ]) - s.socket.close() - await s.connect() - - expect(s.isConnected()).toBeTruthy() - - // only one connect event should fire - expect(onConnected).toHaveBeenCalledTimes(2) - expect(onDisconnected).toHaveBeenCalledTimes(1) - expect(onDisconnecting).toHaveBeenCalledTimes(0) - expect(onConnecting).toHaveBeenCalledTimes(2) - }) + it('fires all events once if connected twice in same tick', async () => { + await Promise.all([ + s.connect(), + s.connect(), + ]) + expect(s.isConnected()).toBeTruthy() + await Promise.all([ + s.disconnect(), + s.disconnect(), + ]) + expect(s.isDisconnected()).toBeTruthy() + // only one connect event should fire + expect(onConnected).toHaveBeenCalledTimes(1) + expect(onDisconnected).toHaveBeenCalledTimes(1) + expect(onDisconnecting).toHaveBeenCalledTimes(1) + expect(onConnecting).toHaveBeenCalledTimes(1) + }) - it('can connect again after disconnect', async () => { - await s.connect() - expect(s.isConnected()).toBeTruthy() - const oldSocket = s.socket - await s.disconnect() - expect(s.isDisconnected()).toBeTruthy() - await s.connect() - expect(s.isConnected()).toBeTruthy() - // check events - expect(onConnected).toHaveBeenCalledTimes(2) - expect(onDisconnected).toHaveBeenCalledTimes(1) - // ensure new socket - expect(s.socket).not.toBe(oldSocket) - }) + it('fires all events minimally if connected twice in same tick then reconnected', async () => { + await Promise.all([ + s.connect(), + s.connect(), + ]) + s.socket.close() + await s.nextConnection() - it('rejects if no url', async () => { - s = new SocketConnection({ - url: undefined, + expect(s.isConnected()).toBeTruthy() + + // only one connect event should fire + expect(onConnected).toHaveBeenCalledTimes(2) + expect(onDisconnected).toHaveBeenCalledTimes(1) + expect(onDisconnecting).toHaveBeenCalledTimes(0) + expect(onConnecting).toHaveBeenCalledTimes(2) }) - onConnected = jest.fn() - s.on('connected', onConnected) - await expect(async () => { + + it('can connect again after disconnect', async () => { await s.connect() - }).rejects.toThrow('not defined') - expect(onConnected).toHaveBeenCalledTimes(0) - }) + expect(s.isConnected()).toBeTruthy() + const oldSocket = s.socket + await s.disconnect() + expect(s.isDisconnected()).toBeTruthy() + await s.connect() + expect(s.isConnected()).toBeTruthy() + // check events + expect(onConnected).toHaveBeenCalledTimes(2) + expect(onDisconnected).toHaveBeenCalledTimes(1) + // ensure new socket + expect(s.socket).not.toBe(oldSocket) + }) - it('rejects if bad url', async () => { - s = new SocketConnection({ - url: 'badurl' + it('rejects if no url', async () => { + expectErrors = 1 + s = new SocketConnection({ + url: undefined, + maxRetries: 3, + }) + onConnected = jest.fn() + s.on('connected', onConnected) + onError = jest.fn() + s.on('error', onError) + await expect(async () => { + await s.connect() + }).rejects.toThrow('not defined') + expect(onConnected).toHaveBeenCalledTimes(0) }) - onConnected = jest.fn() - s.on('connected', onConnected) - await expect(async () => { - await s.connect() - }).rejects.toThrow('badurl') - expect(onConnected).toHaveBeenCalledTimes(0) - }) - it('rejects if cannot connect', async () => { - s = new SocketConnection({ - url: 'wss://streamr.network/nope' + it('rejects if bad url', async () => { + expectErrors = 1 + s = new SocketConnection({ + url: 'badurl', + maxRetries: 3, + }) + onConnected = jest.fn() + s.on('connected', onConnected) + onError = jest.fn() + s.on('error', onError) + await expect(async () => { + await s.connect() + }).rejects.toThrow('badurl') + expect(onConnected).toHaveBeenCalledTimes(0) }) - onConnected = jest.fn() - s.on('connected', onConnected) - await expect(async () => { - await s.connect() - }).rejects.toThrow('Unexpected server response') - expect(onConnected).toHaveBeenCalledTimes(0) - }) - it('disconnect does not error if never connected', async () => { - expect(s.isDisconnected()).toBeTruthy() - await s.disconnect() - expect(s.isDisconnected()).toBeTruthy() - expect(onDisconnected).toHaveBeenCalledTimes(0) - }) + it('rejects if cannot connect', async () => { + expectErrors = 1 + s = new SocketConnection({ + url: 'wss://streamr.network/nope', + maxRetries: 3, + }) + onConnected = jest.fn() + s.on('connected', onConnected) + onError = jest.fn() + s.on('error', onError) + await expect(async () => { + await s.connect() + }).rejects.toThrow('Unexpected server response') + expect(onConnected).toHaveBeenCalledTimes(0) + }) - it('disconnect does not error if already disconnected', async () => { - await s.connect() - await s.disconnect() - expect(s.isDisconnected()).toBeTruthy() - await s.disconnect() - expect(s.isDisconnected()).toBeTruthy() - expect(onDisconnected).toHaveBeenCalledTimes(1) - }) + it('disconnect does not error if never connected', async () => { + expect(s.isDisconnected()).toBeTruthy() + await s.disconnect() + expect(s.isDisconnected()).toBeTruthy() + expect(onDisconnected).toHaveBeenCalledTimes(0) + }) - it('disconnect does not error if already closing', async () => { - await s.connect() - await Promise.all([ - s.disconnect(), - s.disconnect(), - ]) - expect(s.isDisconnected()).toBeTruthy() - expect(onConnected).toHaveBeenCalledTimes(1) - expect(onDisconnected).toHaveBeenCalledTimes(1) - }) + it('disconnect does not error if already disconnected', async () => { + await s.connect() + await s.disconnect() + expect(s.isDisconnected()).toBeTruthy() + await s.disconnect() + expect(s.isDisconnected()).toBeTruthy() + expect(onDisconnected).toHaveBeenCalledTimes(1) + }) - it('can handle disconnect before connect complete', async () => { - await Promise.all([ - expect(async () => ( - s.connect() - )).rejects.toThrow(), - s.disconnect() - ]) - expect(s.isDisconnected()).toBeTruthy() - expect(onConnected).toHaveBeenCalledTimes(1) - expect(onDisconnected).toHaveBeenCalledTimes(1) - }) + it('disconnect does not error if already closing', async () => { + await s.connect() + await Promise.all([ + s.disconnect(), + s.disconnect(), + ]) + expect(s.isDisconnected()).toBeTruthy() + expect(onConnected).toHaveBeenCalledTimes(1) + expect(onDisconnected).toHaveBeenCalledTimes(1) + }) - it('can handle connect before disconnect complete', async () => { - await s.connect() - await Promise.all([ - expect(async () => ( + it('can handle disconnect before connect complete', async () => { + expectErrors = 1 + await Promise.all([ + expect(async () => ( + s.connect() + )).rejects.toThrow(), s.disconnect() - )).rejects.toThrow(), - s.connect() - ]) - expect(s.isConnected()).toBeTruthy() - expect(onConnected).toHaveBeenCalledTimes(2) - expect(onDisconnected).toHaveBeenCalledTimes(1) + ]) + expect(s.isDisconnected()).toBeTruthy() + expect(onConnected).toHaveBeenCalledTimes(0) + expect(onDisconnected).toHaveBeenCalledTimes(0) + }) + + it('can handle connect before disconnect complete', async () => { + expectErrors = 1 + await s.connect() + await Promise.all([ + expect(async () => ( + s.disconnect() + )).rejects.toThrow(), + s.connect() + ]) + expect(s.isConnected()).toBeTruthy() + expect(onConnected).toHaveBeenCalledTimes(2) + expect(onDisconnected).toHaveBeenCalledTimes(1) + }) + + it('emits error but does not disconnect if connect event handler fails', async (done) => { + expectErrors = 1 + const error = new Error('expected error') + s.once('connected', () => { + throw error + }) + s.once('error', async (err) => { + expect(err).toBe(error) + await wait() + expect(s.isConnected()).toBeTruthy() + done() + }) + await s.connect() + expect(s.isConnected()).toBeTruthy() + }) }) - it('emits error but does not disconnect if connect event handler fails', async (done) => { - const error = new Error('expected error') - s.once('connected', () => { - throw error + describe('triggerConnectionOrWait', () => { + it('connects if no autoconnect', async () => { + s.options.autoConnect = false + const task = s.triggerConnectionOrWait() + expect(s.isDisconnected()).toBeTruthy() + await wait(20) + await Promise.all([ + task, + s.connect() + ]) + expect(s.isConnected()).toBeTruthy() }) - s.once('error', async (err) => { - expect(err).toBe(error) - await wait() + + it('connects if autoconnect', async () => { + s.options.autoConnect = true + await s.triggerConnectionOrWait() expect(s.isConnected()).toBeTruthy() - done() }) - await s.connect() - expect(s.isConnected()).toBeTruthy() + + it('errors if connect errors', async () => { + expectErrors = 1 + s.options.autoConnect = true + s.options.url = 'badurl' + await expect(async () => { + await s.triggerConnectionOrWait() + }).rejects.toThrow() + expect(s.isDisconnected()).toBeTruthy() + }) + + it('errors if connect errors without autoconnect', async () => { + expectErrors = 1 + s.options.autoConnect = false + s.options.url = 'badurl' + const task = s.triggerConnectionOrWait() + await wait(20) + await expect(async () => { + await s.connect() + }).rejects.toThrow() + await expect(task).rejects.toThrow() + expect(s.isDisconnected()).toBeTruthy() + }) }) describe('reconnecting', () => { @@ -243,9 +313,10 @@ describe('SocketConnection', () => { }) it('errors if reconnect fails', async (done) => { + expectErrors = 1 await s.connect() s.options.url = 'badurl' - s.once('error', (err) => { + s.on('error', async (err) => { expect(err).toBeTruthy() expect(onConnected).toHaveBeenCalledTimes(1) expect(s.isDisconnected()).toBeTruthy() @@ -278,6 +349,7 @@ describe('SocketConnection', () => { }) it('fails if exceed max retries', async (done) => { + expectErrors = 1 await s.connect() s.options.maxRetries = 2 s.options.url = 'badurl' @@ -289,6 +361,7 @@ describe('SocketConnection', () => { }) it('resets max retries on manual connect after failure', async (done) => { + expectErrors = 1 await s.connect() const goodUrl = s.options.url s.options.maxRetries = 2 @@ -307,6 +380,7 @@ describe('SocketConnection', () => { }) it('can try reconnect after error', async () => { + expectErrors = 2 const goodUrl = s.options.url s.options.url = 'badurl' await expect(async () => ( @@ -410,11 +484,6 @@ describe('SocketConnection', () => { it('waits for reconnecting if sending while reconnecting', async (done) => { await s.connect() - const connect = s.connect.bind(s) - s.connect = async (...args) => { - await wait(0) - return connect(...args) - } s.once('message', ({ data } = {}) => { expect(data).toEqual('test') @@ -426,6 +495,7 @@ describe('SocketConnection', () => { }) it('fails send if reconnect fails', async () => { + expectErrors = 2 // one for auto-reconnect, one for send reconnect await s.connect() // eslint-disable-next-line require-atomic-updates s.options.url = 'badurl' @@ -444,3 +514,4 @@ describe('SocketConnection', () => { }) }) }) + From 6c81cdc77249e598e3ecd949e3913dd135ccd49a Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 2 Sep 2020 16:20:57 -0400 Subject: [PATCH 029/517] Integrate SocketConnection API, pass unit/StreamrClient.test.js. --- src/Publisher.js | 67 ++---------- src/ResendUtil.js | 6 +- src/Resender.js | 11 +- src/StreamrClient.js | 81 +++++---------- src/Subscriber.js | 43 +++++--- src/streams/SocketConnection.js | 38 ++++++- test/unit/StreamrClient.test.js | 176 ++++++++++---------------------- 7 files changed, 166 insertions(+), 256 deletions(-) diff --git a/src/Publisher.js b/src/Publisher.js index 13f28f32b..47c0d8317 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -38,22 +38,6 @@ export default class Publisher { .catch((err) => client.emit('error', err)) ) } - - // On connect/reconnect, send pending subscription requests - this.onConnected = this.onConnected.bind(this) - client.on('connected', this.onConnected) - } - - async onConnected() { - if (!this.client.isConnected()) { return } - try { - // Check pending publish requests - const publishQueueCopy = this.publishQueue.slice(0) - this.publishQueue = [] - publishQueueCopy.forEach((publishFn) => publishFn()) - } catch (err) { - this.client.emit('error', err) - } } async publish(streamObjectOrId, data, timestamp = new Date(), partitionKey = null) { @@ -69,46 +53,6 @@ export default class Publisher { this.msgCreationUtil.createStreamMessage(streamObjectOrId, data, timestampAsNumber, partitionKey), ]) - if (this.client.isConnected()) { - // If connected, emit a publish request - return this._requestPublish(streamMessage, sessionToken) - } - - if (this.client.options.autoConnect) { - if (this.publishQueue.length >= this.client.options.maxPublishQueueSize) { - throw new FailedToPublishError( - streamId, - data, - `publishQueue exceeded maxPublishQueueSize=${this.options.maxPublishQueueSize}`, - ) - } - - const published = new Promise((resolve, reject) => { - this.publishQueue.push(async () => { - let publishRequest - try { - publishRequest = await this._requestPublish(streamMessage, sessionToken) - } catch (err) { - reject(err) - this.client.emit('error', err) - return - } - resolve(publishRequest) - }) - }) - // be sure to trigger connection *after* queueing publish - await this.client.ensureConnected() // await to ensure connection error fails publish - return published - } - - throw new FailedToPublishError( - streamId, - data, - 'Wait for the "connected" event before calling publish, or set autoConnect to true!', - ) - } - - _requestPublish(streamMessage, sessionToken) { const requestId = this.client.resender.resendUtil.generateRequestId() const request = new ControlLayer.PublishRequest({ streamMessage, @@ -116,7 +60,16 @@ export default class Publisher { sessionToken, }) this.debug('_requestPublish: %o', request) - return this.client.connection.send(request) + try { + await this.client.connection.send(request) + } catch (err) { + this.debug('adasd: %o', err) + throw new FailedToPublishError( + streamId, + data, + err + ) + } } getPublisherId() { diff --git a/src/ResendUtil.js b/src/ResendUtil.js index 91702c3f7..938ec4e0f 100644 --- a/src/ResendUtil.js +++ b/src/ResendUtil.js @@ -1,13 +1,15 @@ import EventEmitter from 'eventemitter3' import { ControlLayer } from 'streamr-client-protocol' +import Debug from 'debug' import { uuid } from './utils' const { ControlMessage } = ControlLayer export default class ResendUtil extends EventEmitter { - constructor() { + constructor({ debug } = {}) { super() + this.debug = debug ? debug.extend('Util') : Debug('ResendUtil') this.subForRequestId = {} } @@ -32,12 +34,14 @@ export default class ResendUtil extends EventEmitter { deleteDoneSubsByResponse(response) { // TODO: replace with response.requestId if (response.type === ControlMessage.TYPES.ResendResponseResent || response.type === ControlMessage.TYPES.ResendResponseNoResend) { + this.debug('remove', response.requestId) delete this.subForRequestId[response.requestId] } } registerResendRequestForSub(sub) { const requestId = this.generateRequestId() + this.debug('add', requestId) this.subForRequestId[requestId] = sub sub.addPendingResendRequestId(requestId) return requestId diff --git a/src/Resender.js b/src/Resender.js index 2f4a672f6..ef9e3916b 100644 --- a/src/Resender.js +++ b/src/Resender.js @@ -14,7 +14,9 @@ export default class Resender { this.client = client this.debug = client.debug.extend('Resends') - this.resendUtil = new ResendUtil() + this.resendUtil = new ResendUtil({ + debug: this.debug, + }) this.onResendUtilError = this.onResendUtilError.bind(this) this.resendUtil.on('error', this.onResendUtilError) @@ -114,7 +116,6 @@ export default class Resender { throw new Error('resend: Invalid arguments: options.resend is not given') } - await this.client.ensureConnected() const sub = new HistoricalSubscription({ streamId: options.stream, streamPartition: options.partition || 0, @@ -126,10 +127,10 @@ export default class Resender { debug: this.debug, }) - // TODO remove _addSubscription after uncoupling Subscription and Resend - sub.setState(Subscription.State.subscribed) // eslint-disable-next-line no-underscore-dangle this.client.subscriber._addSubscription(sub) + // TODO remove _addSubscription after uncoupling Subscription and Resend + sub.setState(Subscription.State.subscribed) // eslint-disable-next-line no-underscore-dangle sub.once('initial_resend_done', () => this.client.subscriber._removeSubscription(sub)) await this._requestResend(sub) @@ -141,8 +142,6 @@ export default class Resender { const requestId = this.resendUtil.registerResendRequestForSub(sub) const options = resendOptions || sub.getResendOptions() const sessionToken = await this.client.session.getSessionToken() - // don't bother requesting resend if not connected - if (!this.client.isConnected()) { return } let request if (options.last > 0) { request = new ResendLastRequest({ diff --git a/src/StreamrClient.js b/src/StreamrClient.js index f37519557..17d3a4af4 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -5,7 +5,7 @@ import { Wallet } from 'ethers' import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' import uniqueId from 'lodash.uniqueid' -import Connection from './Connection' +import Connection from './streams/SocketConnection' import Session from './Session' import { waitFor, getVersionString } from './utils' import Publisher from './Publisher' @@ -89,6 +89,18 @@ export default class StreamrClient extends EventEmitter { this.session = new Session(this, this.options.auth) this.connection = connection || new Connection(this.options) + this.connection.on('message', (messageEvent) => { + let controlMessage + try { + controlMessage = ControlLayer.ControlMessage.deserialize(messageEvent.data) + } catch (err) { + this.debug('deserialize error', err) + this.emit('error', err) + return + } + this.connection.emit(controlMessage.type, controlMessage) + }) + this.publisher = new Publisher(this) this.subscriber = new Subscriber(this) this.resender = new Resender(this) @@ -96,6 +108,7 @@ export default class StreamrClient extends EventEmitter { this.connection.on('connected', this.onConnectionConnected) this.connection.on('disconnected', this.onConnectionDisconnected) this.connection.on('error', this.onConnectionError) + this.connection.on(ControlMessage.TYPES.ErrorResponse, this.onErrorResponse) } @@ -127,8 +140,9 @@ export default class StreamrClient extends EventEmitter { this.emit('error', errorObject) } - _onError(...args) { - this.onError(...args) + _onError(err, ...args) { + this.debug('disconnecting due to error', err) + this.onError(err, ...args) this.ensureDisconnected() } @@ -150,41 +164,28 @@ export default class StreamrClient extends EventEmitter { } isConnected() { - return this.connection.state === Connection.State.CONNECTED + return this.connection.isConnected() } isConnecting() { - return this.connection.state === Connection.State.CONNECTING + return this.connection.isConnecting() } isDisconnecting() { - return this.connection.state === Connection.State.DISCONNECTING + return this.connection.isDisconnecting() } isDisconnected() { - return this.connection.state === Connection.State.DISCONNECTED - } - - reconnect() { - return this.connect() + return this.connection.isDisconnected() } async connect() { - try { - if (this.isConnected()) { - throw new Error('Already connected!') - } - - if (this.connection.state === Connection.State.CONNECTING) { - throw new Error('Already connecting!') - } + this.debug('Connecting to %s', this.options.url) + return this.connection.connect() + } - this.debug('Connecting to %s', this.options.url) - await this.connection.connect() - } catch (err) { - this.emit('error', err) - throw err - } + async nextConnection() { + return this.connection.nextConnection() } pause() { @@ -225,38 +226,12 @@ export default class StreamrClient extends EventEmitter { return this.subscriber.getSubscriptions(...args) } - /** - * Starts new connection if disconnected. - * Waits for connection if connecting. - * No-op if already connected. - */ - async ensureConnected() { - if (this.isConnected()) { return Promise.resolve() } - - if (!this.isConnecting()) { - await this.connect() - } - return waitFor(this, 'connected') + return this.connect() } - /** - * Starts disconnection if connected. - * Waits for disconnection if disconnecting. - * No-op if already disconnected. - */ - async ensureDisconnected() { - this.connection.clearReconnectTimeout() - this.publisher.stop() - if (this.isDisconnected()) { return } - - if (this.isDisconnecting()) { - await waitFor(this, 'disconnected') - return - } - - await this.disconnect() + return this.disconnect() } static generateEthereumAccount() { diff --git a/src/Subscriber.js b/src/Subscriber.js index 940fe3564..036341588 100644 --- a/src/Subscriber.js +++ b/src/Subscriber.js @@ -64,7 +64,7 @@ export default class Subscriber { }) } - this._checkAutoDisconnect() + return this._checkAutoDisconnect() } async onClientConnected() { @@ -170,24 +170,24 @@ export default class Subscriber { return sub } - unsubscribe(sub) { + async unsubscribe(sub) { if (!sub || !sub.streamId) { throw new Error('unsubscribe: please give a Subscription object as an argument!') } const sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) - // If this is the last subscription for this stream-partition, unsubscribe the client too if (sp && sp.getSubscriptions().length === 1 - && this.client.isConnected() && sub.getState() === Subscription.State.subscribed) { sub.setState(Subscription.State.unsubscribing) - this._requestUnsubscribe(sub) - } else if (sub.getState() !== Subscription.State.unsubscribing && sub.getState() !== Subscription.State.unsubscribed) { + return this._requestUnsubscribe(sub) + } + + if (sub.getState() !== Subscription.State.unsubscribing && sub.getState() !== Subscription.State.unsubscribed) { // Else the sub can be cleaned off immediately this._removeSubscription(sub) sub.setState(Subscription.State.unsubscribed) - this._checkAutoDisconnect() + return this._checkAutoDisconnect() } } @@ -315,6 +315,7 @@ export default class Subscriber { }) this.debug('_requestSubscribe: subscribing client: %o', request) sp.setSubscribing(true) + await this.client.connection.nextConnection() await this.client.connection.send(request).catch((err) => { sub.setState(Subscription.State.unsubscribed) this.client.emit('error', `Failed to send subscribe request: ${err.stack}`) @@ -332,13 +333,14 @@ export default class Subscriber { sub.setState(Subscription.State.subscribed) this.client.handleError(`Failed to send unsubscribe request: ${err.stack}`) }) + return this._checkAutoDisconnect() } - _checkAutoDisconnect() { + async _checkAutoDisconnect() { // Disconnect if no longer subscribed to any streams if (this.client.options.autoDisconnect && Object.keys(this.subscribedStreamPartitions).length === 0) { this.debug('Disconnecting due to no longer being subscribed to any streams') - this.client.disconnect() + return this.client.disconnect() } } @@ -369,14 +371,25 @@ export default class Subscriber { } async _resendAndSubscribe(sub) { - if (sub.getState() === Subscription.State.subscribing || sub.resending) { return } + if (sub.getState() === Subscription.State.subscribing || sub.resending) { + return Promise.resolve() + } + sub.setState(Subscription.State.subscribing) - // Once subscribed, ask for a resend - sub.once('subscribed', () => { - if (!sub.hasResendOptions()) { return } - // eslint-disable-next-line no-underscore-dangle - this.client.resender._requestResend(sub) + if (!sub.hasResendOptions()) { + return this._requestSubscribe(sub) + } + + const onSubscribed = new Promise((resolve, reject) => { + // Once subscribed, ask for a resend + sub.once('subscribed', () => { + // eslint-disable-next-line no-underscore-dangle + resolve(this.client.resender._requestResend(sub)) + }) + sub.once('error', reject) }) await this._requestSubscribe(sub) + + return onSubscribed } } diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js index 747359f5e..2cfc74fd5 100644 --- a/src/streams/SocketConnection.js +++ b/src/streams/SocketConnection.js @@ -109,7 +109,6 @@ export default class SocketConnection extends EventEmitter { this.debug('emit', event, args) return super.emit(event, ...args) } - this.debug('emit', event) // note if event handler is async and it rejects we're kinda hosed @@ -319,6 +318,26 @@ export default class SocketConnection extends EventEmitter { this.emit('connected') } + async nextDisconnection() { + if (this.isDisconnected()) { + return Promise.resolve() + } + + return new Promise((resolve, reject) => { + let onError + const onDisconnected = () => { + this.off('error', onError) + resolve() + } + onError = (err) => { + this.off('disconnected', onDisconnected) + reject(err) + } + this.once('disconnected', onDisconnected) + this.once('error', onError) + }) + } + async disconnect() { this.debug('disconnect()') this.shouldConnect = false @@ -390,15 +409,15 @@ export default class SocketConnection extends EventEmitter { } async maybeConnect() { - this.debug('maybeConnect', this.options.autoConnect || this.shouldConnect) + this.debug('maybeConnect', this.options.autoConnect, this.shouldConnect) if (this.options.autoConnect || this.shouldConnect) { // should be open, so wait for open or trigger new open await this.connect() } } - async send(msg) { - this.debug('send()') + async needsConnection() { + this.debug('needsConnection') await this.maybeConnect() if (!this.isConnected()) { // note we can't just let socket.send fail, @@ -406,7 +425,18 @@ export default class SocketConnection extends EventEmitter { // to be uncatchable in the browser throw new Error('connection closed or closing') } + } + + async send(msg) { + this.debug('send()') + if (!this.isConnected()) { + // shortcut await if connected + await this.needsConnection() + } + return this._send(msg) + } + async _send(msg) { return new Promise((resolve, reject) => { this.debug('>> %s', msg) // promisify send diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index 6d8c2df53..db664aa57 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -6,14 +6,16 @@ import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' import FailedToPublishError from '../../src/errors/FailedToPublishError' -import Connection from '../../src/Connection' import Subscription from '../../src/Subscription' +import Connection from '../../src/streams/SocketConnection' // import StreamrClient from '../../src/StreamrClient' import { uid } from '../utils' // eslint-disable-next-line import/no-named-as-default-member import StubbedStreamrClient from './StubbedStreamrClient' +/* eslint-disable no-underscore-dangle */ + const { ControlMessage, BroadcastMessage, @@ -103,36 +105,11 @@ describe('StreamrClient', () => { } function createConnectionMock() { - const c = new EventEmitter() - c.state = Connection.State.DISCONNECTED + const c = new Connection({}) c.expectedMessagesToSend = [] - c.connect = () => new Promise((resolve) => { - mockDebug('Connection mock: connecting') - c.state = Connection.State.CONNECTING - async(() => { - mockDebug('Connection mock: connected') - c.state = Connection.State.CONNECTED - c.emit('connected') - resolve() - }) - }) - - c.clearReconnectTimeout = () => {} - - c.disconnect = () => new Promise((resolve) => { - mockDebug('Connection mock: disconnecting') - c.state = Connection.State.DISCONNECTING - async(() => { - mockDebug('Connection mock: disconnected') - c.state = Connection.State.DISCONNECTED - c.emit('disconnected') - resolve() - }) - }) - - c.send = jest.fn(async (request) => { + c._send = jest.fn(async (request) => { requests.push(request) }) @@ -151,7 +128,7 @@ describe('StreamrClient', () => { function mockSubscription(...opts) { let sub - connection.send = jest.fn(async (request) => { + connection._send = jest.fn(async (request) => { requests.push(request) await wait() if (request.type === ControlMessage.TYPES.SubscribeRequest) { @@ -184,10 +161,13 @@ describe('StreamrClient', () => { autoDisconnect: false, verifySignatures: 'never', retryResendAfter: STORAGE_DELAY, + url: 'wss://echo.websocket.org/', auth: { sessionToken: 'session-token', }, }, connection) + + connection.options = client.options errors = [] requests = [] client.on('error', onError) @@ -214,7 +194,7 @@ describe('StreamrClient', () => { it('should not send anything if not subscribed to anything', async () => { await client.ensureConnected() - expect(connection.send).not.toHaveBeenCalled() + expect(connection._send).not.toHaveBeenCalled() }) it('should send pending subscribes', async () => { @@ -222,30 +202,30 @@ describe('StreamrClient', () => { await client.ensureConnected() await wait() - expect(connection.send.mock.calls).toHaveLength(1) - expect(connection.send.mock.calls[0][0]).toMatchObject({ + expect(connection._send.mock.calls).toHaveLength(1) + expect(connection._send.mock.calls[0][0]).toMatchObject({ streamId: 'stream1', streamPartition, sessionToken, }) }) - it('should send pending subscribes when disconnected and then reconnected', async () => { + it.skip('should send pending subscribes when disconnected and then reconnected', async () => { client.subscribe('stream1', () => {}).on('error', onError) await client.ensureConnected() - await connection.disconnect() + connection.socket.close() await client.ensureConnected() await wait(100) - expect(connection.send.mock.calls).toHaveLength(2) + expect(connection._send.mock.calls).toHaveLength(2) // On connect - expect(connection.send.mock.calls[0][0]).toMatchObject({ + expect(connection._send.mock.calls[0][0]).toMatchObject({ streamId: 'stream1', streamPartition, sessionToken, }) // On reconnect - expect(connection.send.mock.calls[1][0]).toMatchObject({ + expect(connection._send.mock.calls[1][0]).toMatchObject({ streamId: 'stream1', streamPartition, sessionToken, @@ -263,13 +243,13 @@ describe('StreamrClient', () => { }) it('does not remove subscriptions', async () => { - const sub = client.subscribe('stream1', () => {}).on('error', onError) + const sub = client.subscribe('stream1', () => {}) await connection.disconnect() expect(client.getSubscriptions(sub.streamId)).toEqual([sub]) }) it('sets subscription state to unsubscribed', async () => { - const sub = client.subscribe('stream1', () => {}).on('error', onError) + const sub = client.subscribe('stream1', () => {}) await connection.disconnect() expect(sub.getState()).toEqual(Subscription.State.unsubscribed) }) @@ -315,8 +295,8 @@ describe('StreamrClient', () => { await wait(STORAGE_DELAY) sub.stop() await wait() - expect(connection.send.mock.calls).toHaveLength(2) // sub + resend - expect(connection.send.mock.calls[1][0]).toMatchObject({ + expect(connection._send.mock.calls).toHaveLength(2) // sub + resend + expect(connection._send.mock.calls[1][0]).toMatchObject({ type: ControlMessage.TYPES.ResendLastRequest, streamId: sub.streamId, streamPartition: sub.streamPartition, @@ -393,7 +373,7 @@ describe('StreamrClient', () => { }) ] // eslint-disable-next-line semi-style - ;[connection.send.mock.calls[1][0], connection.send.mock.calls[2][0]].forEach((actual, index) => { + ;[connection._send.mock.calls[1][0], connection._send.mock.calls[2][0]].forEach((actual, index) => { const expected = expectedResponses[index] expect(actual).toMatchObject({ requestId: expected.requestId, @@ -417,14 +397,12 @@ describe('StreamrClient', () => { }) it('removes the subscription', async () => { - client.unsubscribe(sub) - await wait() + await client.unsubscribe(sub) expect(client.getSubscriptions(sub.streamId)).toEqual([]) }) it('sets Subscription state to unsubscribed', async () => { - client.unsubscribe(sub) - await wait() + await client.unsubscribe(sub) expect(sub.getState()).toEqual(Subscription.State.unsubscribed) }) @@ -435,10 +413,8 @@ describe('StreamrClient', () => { }) it('calls connection.disconnect() when no longer subscribed to any streams', async () => { - const disconnect = jest.spyOn(connection, 'disconnect') - client.unsubscribe(sub) - await wait(100) - expect(disconnect).toHaveBeenCalled() + await client.unsubscribe(sub) + expect(client.isDisconnected()).toBeTruthy() }) }) @@ -448,10 +424,8 @@ describe('StreamrClient', () => { }) it('should not disconnect if autoDisconnect is set to false', async () => { - const disconnect = jest.spyOn(connection, 'disconnect') - client.unsubscribe(sub) - await wait(100) - expect(disconnect).not.toHaveBeenCalled() + await client.unsubscribe(sub) + expect(client.isConnected()).toBeTruthy() }) }) }) @@ -527,7 +501,9 @@ describe('StreamrClient', () => { last: 5, }, }, () => {}) - .once('subscribed', () => done()) + .once('subscribed', () => { + done() + }) }) it('should call the message handler of specified Subscription', async () => { @@ -535,7 +511,6 @@ describe('StreamrClient', () => { sub.handleResentMessage = jest.fn() const { requestId } = requests[requests.length - 1] expect(requestId).toBeTruthy() - // this sub's handler must not be called const sub2 = mockSubscription('stream1', () => {}) sub2.handleResentMessage = jest.fn() @@ -795,50 +770,16 @@ describe('StreamrClient', () => { expect(result).toBeInstanceOf(Promise) await result }) - - it('should call connection.connect()', () => { - connection.connect = jest.fn(async () => {}) - client.connect() - expect(connection.connect).toHaveBeenCalledTimes(1) - }) - - it('should reject promise while connecting', async (done) => { - client.onError = jest.fn() - connection.state = Connection.State.CONNECTING - client.once('error', (err) => { - errors.pop() - expect(err).toMatchObject({ - message: 'Already connecting!' - }) - expect(client.onError).toHaveBeenCalledTimes(1) - done() - }) - await expect(() => ( - client.connect() - )).rejects.toThrow() - }) - - it('should reject promise when connected', async (done) => { - client.onError = jest.fn() - connection.state = Connection.State.CONNECTED - client.once('error', (err) => { - errors.pop() - expect(err).toMatchObject({ - message: 'Already connected!' - }) - expect(client.onError).toHaveBeenCalledTimes(1) - done() - }) - await expect(() => ( - client.connect() - )).rejects.toThrow() - }) }) describe('resend()', () => { + beforeEach(() => { + client.options.autoConnect = true + }) + async function mockResend(...opts) { let sub - connection.send = jest.fn(async (request) => { + connection._send = jest.fn(async (request) => { requests.push(request) await wait() if (request.type === ControlMessage.TYPES.SubscribeRequest) { @@ -871,7 +812,7 @@ describe('StreamrClient', () => { }, () => {}) await client.pause() await client.connect() - expect(connection.send.mock.calls.filter(([arg]) => arg.type === ControlMessage.TYPES.SubscribeRequest)).toHaveLength(0) + expect(connection._send.mock.calls.filter(([arg]) => arg.type === ControlMessage.TYPES.SubscribeRequest)).toHaveLength(0) }) it('should not send SubscribeRequest after ResendResponseNoResend on reconnection', async () => { @@ -881,6 +822,7 @@ describe('StreamrClient', () => { last: 10 } }, () => {}) + const { requestId } = requests[requests.length - 1] const resendResponse = new ResendResponseNoResend({ streamId: sub.streamId, @@ -890,7 +832,7 @@ describe('StreamrClient', () => { connection.emitMessage(resendResponse) await client.pause() await client.connect() - expect(connection.send.mock.calls.filter(([arg]) => arg.type === ControlMessage.TYPES.SubscribeRequest)).toHaveLength(0) + expect(connection._send.mock.calls.filter(([arg]) => arg.type === ControlMessage.TYPES.SubscribeRequest)).toHaveLength(0) }) it('should not send SubscribeRequest after ResendResponseResent on reconnection', async () => { @@ -1014,7 +956,7 @@ describe('StreamrClient', () => { })) }) - it('sends only one subscribe request to server even if there are multiple subscriptions for same stream', async () => { + it('sends just one subscribe request to server even if there are multiple subscriptions for same stream', async () => { const sub = mockSubscription('stream1', () => {}) const sub2 = mockSubscription('stream1', () => {}) await Promise.all([ @@ -1106,7 +1048,7 @@ describe('StreamrClient', () => { last: 5, }, }, () => {}) - connection.send = async (request) => { + connection._send = async (request) => { requests.push(request) await wait() if (request.type === ControlMessage.TYPES.SubscribeRequest) { @@ -1259,8 +1201,7 @@ describe('StreamrClient', () => { }) it('sends an unsubscribe request', async () => { - client.unsubscribe(sub) - await wait() + await client.unsubscribe(sub) expect(requests).toHaveLength(2) const lastRequest = requests[requests.length - 1] expect(lastRequest).toEqual(new UnsubscribeRequest({ @@ -1276,8 +1217,7 @@ describe('StreamrClient', () => { stream: sub.streamId, }, () => {}) - client.unsubscribe(sub) - await wait() + await client.unsubscribe(sub) expect(requests).toHaveLength(1) }) @@ -1287,9 +1227,8 @@ describe('StreamrClient', () => { }, () => {}) sub2.once('subscribed', async () => { - client.unsubscribe(sub) - client.unsubscribe(sub2) - await wait() + await client.unsubscribe(sub) + await client.unsubscribe(sub2) const lastRequest = requests[requests.length - 1] expect(lastRequest).toEqual(new UnsubscribeRequest({ streamId: sub.streamId, @@ -1302,9 +1241,8 @@ describe('StreamrClient', () => { }) it('does not send an unsubscribe request again if unsubscribe is called multiple times', async () => { - client.unsubscribe(sub) - client.unsubscribe(sub) - await wait() + await client.unsubscribe(sub) + await client.unsubscribe(sub) expect(requests).toHaveLength(2) const lastRequest = requests[requests.length - 1] expect(lastRequest).toEqual(new UnsubscribeRequest({ @@ -1319,25 +1257,23 @@ describe('StreamrClient', () => { const handler = jest.fn() sub.on('unsubscribed', handler) - client.unsubscribe(sub) - await wait() + await client.unsubscribe(sub) expect(sub.getState()).toEqual(Subscription.State.unsubscribed) - client.unsubscribe(sub) - await wait() + await client.unsubscribe(sub) expect(handler).toHaveBeenCalledTimes(1) }) it('throws if no Subscription is given', () => { - expect(() => { - client.unsubscribe() - }).toThrow() + expect(async () => { + await client.unsubscribe() + }).rejects.toThrow() }) it('throws if Subscription is of wrong type', () => { - expect(() => { - client.unsubscribe(sub.streamId) - }).toThrow() + expect(async () => { + await client.unsubscribe(sub.streamId) + }).rejects.toThrow() }) }) @@ -1428,7 +1364,7 @@ describe('StreamrClient', () => { const pubMsg = { value: uid('msg'), } - await expect(() => ( + await expect(async () => ( client.publish('stream1', pubMsg) )).rejects.toThrow(FailedToPublishError) }) From a2783b6aeccefb0c114817785768125f050e3cdb Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 2 Sep 2020 16:24:20 -0400 Subject: [PATCH 030/517] Remove old Connection. --- src/Connection.js | 169 ------------------------- test/unit/Connection.test.js | 233 ----------------------------------- 2 files changed, 402 deletions(-) delete mode 100644 src/Connection.js delete mode 100644 test/unit/Connection.test.js diff --git a/src/Connection.js b/src/Connection.js deleted file mode 100644 index 41b0f98cb..000000000 --- a/src/Connection.js +++ /dev/null @@ -1,169 +0,0 @@ -import EventEmitter from 'eventemitter3' -import debugFactory from 'debug' -import uniqueId from 'lodash.uniqueid' -import WebSocket from 'ws' -import { ControlLayer } from 'streamr-client-protocol' - -class Connection extends EventEmitter { - constructor(options, socket) { - super() - if (!options.url) { - throw new Error('URL is not defined!') - } - const id = uniqueId('Connection') - if (options.debug) { - this.debug = options.debug.extend(id) - } else { - this.debug = debugFactory(`StreamrClient::${id}`) - } - this.options = options - this.state = Connection.State.DISCONNECTED - this.socket = socket - this._reconnectTimeout = null - } - - updateState(state) { - this.state = state - this.emit(state) - } - - async connect() { - if (this.state === Connection.State.CONNECTING) { - throw new Error('Already connecting!') - } - - if (this.state === Connection.State.CONNECTED) { - throw new Error('Already connected!') - } - - if (this.state === Connection.State.DISCONNECTING) { - return new Promise((resolve) => { - this.once('disconnected', () => resolve(this.connect())) - }) - } - - if (!this.socket || this.socket.readyState === WebSocket.CLOSED) { - this.debug('Trying to open new websocket to %s', this.options.url) - this.socket = new WebSocket(this.options.url) - } - - this.socket.binaryType = 'arraybuffer' - this.socket.events = new EventEmitter() - - this.socket.onopen = () => this.socket.events.emit('open') - this.socket.onclose = () => this.socket.events.emit('close') - this.socket.onerror = () => this.socket.events.emit('error') - - this.updateState(Connection.State.CONNECTING) - - this.socket.events.on('open', () => { - this.debug('Connected to ', this.options.url) - this.updateState(Connection.State.CONNECTED) - }) - - this.socket.events.on('error', (err) => { - this.debug('Error in websocket.') - if (err) { - console.error(err) - } - this.socket.close() - }) - - this.socket.events.on('close', () => { - if (this.state !== Connection.State.DISCONNECTING) { - this.debug('Connection lost. Attempting to reconnect') - clearTimeout(this._reconnectTimeout) - this._reconnectTimeout = setTimeout(() => { - this.connect().catch((err) => { - console.error(err) - }) - }, 2000) - } - - this.updateState(Connection.State.DISCONNECTED) - }) - - this.socket.onmessage = (messageEvent) => { - let controlMessage - try { - this.debug('<< %s', messageEvent.data) - controlMessage = ControlLayer.ControlMessage.deserialize(messageEvent.data) - } catch (err) { - this.emit('error', err) - return - } - this.emit(controlMessage.type, controlMessage) - } - - return new Promise((resolve) => { - this.socket.events.once('open', () => { - resolve() - }) - }) - } - - clearReconnectTimeout() { - clearTimeout(this._reconnectTimeout) - } - - async disconnect() { - this.clearReconnectTimeout() - - if (this.state === Connection.State.DISCONNECTING) { - throw new Error('Already disconnecting!') - } - - if (this.state === Connection.State.DISCONNECTED) { - throw new Error('Already disconnected!') - } - - if (this.socket === undefined) { - throw new Error('Something is wrong: socket is undefined!') - } - - if (this.state === Connection.State.CONNECTING) { - return new Promise((resolve) => { - this.once('connected', () => resolve(this.disconnect().catch((err) => console.error(err)))) - }) - } - - return new Promise((resolve) => { - this.updateState(Connection.State.DISCONNECTING) - this.socket.events.once('close', resolve) - this.socket.close() - }) - } - - async send(controlLayerRequest) { - return new Promise((resolve, reject) => { - try { - const serialized = controlLayerRequest.serialize() - this.debug('>> %s', serialized) - this.socket.send(serialized, (err) => { - if (err) { - reject(err) - } else { - resolve(controlLayerRequest) - } - }) - - if (process.browser) { - resolve(controlLayerRequest) - } - } catch (err) { - this.emit('error', err) - reject(err) - } - }) - } -} - -Connection.State = { - DISCONNECTED: 'disconnected', - CONNECTING: 'connecting', - CONNECTED: 'connected', - DISCONNECTING: 'disconnecting', -} - -export default Connection - diff --git a/test/unit/Connection.test.js b/test/unit/Connection.test.js deleted file mode 100644 index b2e08d267..000000000 --- a/test/unit/Connection.test.js +++ /dev/null @@ -1,233 +0,0 @@ -import { ControlLayer, MessageLayer } from 'streamr-client-protocol' -import { wait } from 'streamr-test-utils' - -import Connection from '../../src/Connection' - -const { UnicastMessage, ControlMessage } = ControlLayer -const { StreamMessage, MessageIDStrict, MessageRef } = MessageLayer - -describe('Connection', () => { - let conn - beforeEach(() => { - conn = new Connection({ - url: 'foo', - }, { - on: jest.fn(), - close: jest.fn(), - }) - }) - - describe('initial state', () => { - it('should be correct', () => { - expect(conn.state).toEqual(Connection.State.DISCONNECTED) - }) - }) - - describe('connect()', () => { - it('returns a promise and resolves it when connected', async () => { - const result = conn.connect() - expect(result instanceof Promise).toBeTruthy() - conn.socket.onopen() - await result - }) - - it('adds listeners to socket', () => { - conn.connect() - expect(conn.socket.onopen != null).toBeTruthy() - expect(conn.socket.onclose != null).toBeTruthy() - expect(conn.socket.onmessage != null).toBeTruthy() - expect(conn.socket.onerror != null).toBeTruthy() - }) - - it('should report correct state when connecting', () => { - conn.connect() - expect(conn.state).toEqual(Connection.State.CONNECTING) - }) - - it('should report correct state flag when connected', () => { - conn.connect() - conn.socket.onopen() - expect(conn.state).toEqual(Connection.State.CONNECTED) - }) - - it('should reject the promise if already connected', async () => { - conn.connect() - conn.socket.onopen() - expect(conn.state).toEqual(Connection.State.CONNECTED) - - await expect(() => ( - conn.connect() - )).rejects.toThrow() - expect(conn.state).toEqual(Connection.State.CONNECTED) - }) - - it('should resolve the promise', async () => { - const task = conn.connect() - conn.socket.onopen() - conn.socket.onopen() - await task - }) - }) - - describe('disconnect()', () => { - beforeEach(() => { - conn.connect() - conn.socket.onopen() - expect(conn.state).toEqual(Connection.State.CONNECTED) - }) - - afterEach(() => { - conn.disconnect().catch(() => { - // ignore - }) - }) - - it('returns a promise and resolves it when disconnected', () => { - const result = conn.disconnect() - expect(result instanceof Promise).toBeTruthy() - conn.socket.onclose() - return result - }) - - it('should call socket.close()', () => { - conn.disconnect() - expect(conn.socket.close).toHaveBeenCalledTimes(1) - }) - - it('should report correct state when disconnecting', () => { - conn.disconnect() - expect(conn.state).toEqual(Connection.State.DISCONNECTING) - }) - - it('should report correct state flag when connected', () => { - conn.disconnect() - conn.socket.onclose() - expect(conn.state).toEqual(Connection.State.DISCONNECTED) - }) - - it('should reject the promise if already disconnected', async () => { - conn.disconnect() - conn.socket.onclose() - expect(conn.state).toEqual(Connection.State.DISCONNECTED) - - await expect(() => conn.disconnect()).rejects.toThrow() - expect(conn.state).toEqual(Connection.State.DISCONNECTED) - }) - - it('should resolve the promise', async () => { - const task = conn.disconnect() - conn.socket.onclose() - conn.socket.onclose() - await task - }) - }) - - describe('send()', () => { - beforeEach(() => { - conn.connect() - }) - - it('sends the serialized message over the socket', () => { - const request = { - serialize: jest.fn(() => 'foo') - } - conn.socket.send = jest.fn() - - conn.send(request) - expect(request.serialize).toHaveBeenCalledTimes(1) - expect(conn.socket.send).toHaveBeenCalledWith('foo', expect.any(Function)) - }) - - it('emits error event if socket.send throws', (done) => { - const request = { - serialize: jest.fn() - } - conn.socket.send = () => { - throw new Error('test') - } - - conn.once('error', (err) => { - expect(err.message).toEqual('test') - done() - }) - conn.send(request).catch((err) => { - // hm, this probably should *either* emit an error or reject - expect(err.message).toEqual('test') - }) - }) - }) - - describe('event handling on socket', () => { - beforeEach(() => { - conn.connect() - conn.socket.onopen() - }) - - describe('message', () => { - it('emits events named by messageTypeName and the ControlMessage as an argument', (done) => { - const timestamp = Date.now() - const content = { - hello: 'world', - } - conn.once(ControlMessage.TYPES.UnicastMessage, (message) => { - expect(message instanceof UnicastMessage).toBeTruthy() - expect(message.streamMessage.getTimestamp()).toEqual(timestamp) - expect(message.streamMessage.getParsedContent().hello).toEqual('world') - expect(message.requestId).toEqual('requestId') - done() - }) - - const message = new UnicastMessage({ - requestId: 'requestId', - streamMessage: new StreamMessage({ - messageId: new MessageIDStrict('streamId', 0, timestamp, 0, '', ''), - prevMsgRef: new MessageRef(timestamp - 100, 0), - content, - messageType: StreamMessage.MESSAGE_TYPES.MESSAGE, - encryptionType: StreamMessage.ENCRYPTION_TYPES.NONE, - signatureType: StreamMessage.SIGNATURE_TYPES.NONE, - }) - }) - - conn.socket.onmessage({ - data: message.serialize(), - }) - }) - - it('does not emit an error event when a message contains invalid json', (done) => { - const onError = jest.fn() - conn.once('error', onError) // shouldn't error because content itself not deserialized in connection - conn.once(ControlMessage.TYPES.UnicastMessage, () => { - expect(onError).not.toHaveBeenCalled() - done() - }) - const timestamp = Date.now() - - const message = new UnicastMessage({ - requestId: 'requestId', - streamMessage: new StreamMessage({ - messageId: new MessageIDStrict('streamId', 0, timestamp, 0, '', ''), - prevMsgRef: null, - content: '{', // bad json - messageType: StreamMessage.MESSAGE_TYPES.MESSAGE, - encryptionType: StreamMessage.ENCRYPTION_TYPES.NONE, - signatureType: StreamMessage.SIGNATURE_TYPES.NONE, - }) - }) - const data = message.serialize() - conn.socket.onmessage({ - data, - }) - }) - }) - - describe('close', () => { - it('tries to reconnect after 2 seconds', async () => { - conn.connect = jest.fn(async () => {}) - conn.socket.events.emit('close') - await wait(2100) - expect(conn.connect).toHaveBeenCalledTimes(1) - }) - }) - }) -}) From 0acef3c6624554980dcd64150cd0db690887c2ae Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 3 Sep 2020 13:39:07 -0400 Subject: [PATCH 031/517] Test connect/disconnect in event handlers. --- src/streams/SocketConnection.js | 176 +++++++++++++++++++-------- test/unit/streams/Connection.test.js | 87 +++++++++++-- 2 files changed, 205 insertions(+), 58 deletions(-) diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js index 2cfc74fd5..6777df74f 100644 --- a/src/streams/SocketConnection.js +++ b/src/streams/SocketConnection.js @@ -5,25 +5,49 @@ import debugFactory from 'debug' import uniqueId from 'lodash.uniqueid' import WebSocket from 'ws' +class ConnectionError extends Error { + constructor(err, ...args) { + if (err instanceof ConnectionError) { + return err + } + + if (err && err.stack) { + const { message, stack } = err + super(message, ...args) + this.stack = stack + } else { + super(err, ...args) + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor) + } + } + } +} + +let openSockets = 0 + async function OpenWebSocket(url, ...args) { return new Promise((resolve, reject) => { try { if (!url) { - throw new Error('URL is not defined!') + throw new ConnectionError('URL is not defined!') } const socket = new WebSocket(url, ...args) + socket.id = uniqueId('socket') socket.binaryType = 'arraybuffer' - socket.onopen = () => resolve(socket) + let opened = 0 + socket.onopen = () => { + opened = 1 + openSockets += opened + resolve(socket) + } let error socket.onclose = () => { - if (error) { - reject(error) - return - } - reject(new Error('socket closed')) + openSockets -= opened + reject(new ConnectionError(error || 'socket closed')) } socket.onerror = (event) => { - error = event.error || new Error(event) + error = new ConnectionError(event.error || event) } return } catch (err) { @@ -72,6 +96,10 @@ async function CloseWebSocket(socket) { */ export default class SocketConnection extends EventEmitter { + static getOpen() { + return openSockets + } + constructor(options) { super() this.options = options @@ -106,20 +134,39 @@ export default class SocketConnection extends EventEmitter { emit(event, ...args) { if (event === 'error') { - this.debug('emit', event, args) - return super.emit(event, ...args) + let [err] = args + const [, ...rest] = args + err = new ConnectionError(err) + this.debug('emit', event, ...args) + return super.emit(event, err, ...rest) } this.debug('emit', event) // note if event handler is async and it rejects we're kinda hosed // until node lands unhandledrejection support // in eventemitter + let result try { - return super.emit(event, ...args) + result = super.emit(event, ...args) } catch (err) { super.emit('error', err) return true } + return result + } + + emitTransition(event, ...args) { + const previousConnectionState = this.shouldConnect + const result = this.emit(event, ...args) + // if event emitter changed shouldConnect state, throw + if (!!previousConnectionState !== !!this.shouldConnect) { + this.debug('transitioned in event handler %s: shouldConnect %s -> %s', event, previousConnectionState, this.shouldConnect) + if (this.shouldConnect) { + throw new ConnectionError(`connect called in ${event} handler`) + } + throw new ConnectionError(`disconnect called in ${event} handler`) + } + return result } async reconnect(...args) { @@ -160,7 +207,7 @@ export default class SocketConnection extends EventEmitter { const { maxRetries } = this.options // try again this.debug('attempting to reconnect %s of %s', retryCount, maxRetries) - this.emit('reconnecting') + this.emitTransition('reconnecting') return this._connectOnce(...args).then((value) => { this.debug('reconnect %s of %s successful', retryCount, maxRetries) // reset retry state @@ -219,10 +266,11 @@ export default class SocketConnection extends EventEmitter { throw error }) }).catch((error) => { + const err = new ConnectionError(error) if (!this._isReconnecting) { - this.emit('error', error) + this.emit('error', err) } - throw error + throw err }) .finally(() => { if (this.initialConnectTask === initialConnectTask) { @@ -233,22 +281,10 @@ export default class SocketConnection extends EventEmitter { return this.initialConnectTask } - async reconnectOnUnexpectedClose() { - if (!this.shouldConnect) { - return - } - - this.debug('unexpected close') - await this.reconnect().catch((error) => { - this.debug('failed reconnect after connected', error) - this.emit('error', error) - }) - } - async _connectOnce() { const { debug } = this if (!this.shouldConnect) { - throw new Error('disconnected before connected') + throw new ConnectionError('disconnected before connected') } if (this.isConnected()) { @@ -266,10 +302,22 @@ export default class SocketConnection extends EventEmitter { return this.connectTask } - debug('connect()', this.socket && this.socket.readyState) const connectTask = this._connect().then((value) => { - // reconnect on unexpected failure - this.socket.addEventListener('close', () => this.reconnectOnUnexpectedClose()) + const debugCurrent = this.debug + const { socket } = this // capture so we can ignore if not current + socket.addEventListener('close', async () => { + // reconnect on unexpected failure + if ((this.socket && socket !== this.socket) || !this.shouldConnect) { + return + } + + debug('unexpected close') + // eslint-disable-next-line promise/no-nesting + await this.reconnect().catch((error) => { + debugCurrent('failed reconnect after connected', error) + this.emit('error', error) + }) + }) return value }).finally(() => { this.connectTask = undefined @@ -282,26 +330,48 @@ export default class SocketConnection extends EventEmitter { async _connect() { this.debug = this._debug.extend(uniqueId('socket')) const { debug } = this - this.emit('connecting') + debug('connect()', this.socket && this.socket.readyState) + this.emitTransition('connecting') + + if (!this.shouldConnect) { + // was disconnected in connecting event + throw new ConnectionError('disconnected before connected') + } + const socket = await OpenWebSocket(this.options.url) debug('connected') - this.socket = socket if (!this.shouldConnect) { + await CloseWebSocket(socket) // was disconnected while connecting - throw new Error('disconnected before connected') + throw new ConnectionError('disconnected before connected') } + this.socket = socket + socket.addEventListener('message', (messageEvent, ...args) => { if (this.socket !== socket) { return } debug('<< %s', messageEvent && messageEvent.data) this.emit('message', messageEvent, ...args) }) - socket.addEventListener('close', (event) => { + socket.addEventListener('close', () => { debug('closed') - if (this.socket === socket) { - this.socket = undefined - this.emit('disconnected', event) + + if (this.socket !== socket) { + if (this.debug === debug) { + this.debug = this._debug + } + return + } + + this.socket = undefined + try { + this.emit('disconnected') + } catch (err) { + this.emit('error', err) + } + + if (this.debug === debug) { this.debug = this._debug } }) @@ -309,13 +379,13 @@ export default class SocketConnection extends EventEmitter { socket.addEventListener('error', (err) => { debug('error', this.socket !== socket, err) if (this.socket !== socket) { return } - const error = err.error || new Error(err) + const error = new ConnectionError(err.error || err) if (!this._isReconnecting) { this.emit('error', error) } }) - this.emit('connected') + this.emitTransition('connected') } async nextDisconnection() { @@ -339,11 +409,11 @@ export default class SocketConnection extends EventEmitter { } async disconnect() { - this.debug('disconnect()') this.shouldConnect = false if (this.disconnectTask) { - await this.disconnectTask + return this.disconnectTask } + this.debug('disconnect()') const disconnectTask = this._disconnect() .catch((err) => { this.emit('error', err) @@ -358,6 +428,7 @@ export default class SocketConnection extends EventEmitter { } async _disconnect() { + this.debug('_disconnect()') if (this.connectTask) { try { await this.connectTask @@ -367,17 +438,21 @@ export default class SocketConnection extends EventEmitter { } if (this.shouldConnect) { - throw new Error('connected before disconnected') + throw new ConnectionError('connect before disconnect started') } if (this.isConnected()) { - this.emit('disconnecting') + this.emitTransition('disconnecting') + } + + if (this.shouldConnect) { + throw new ConnectionError('connect while disconnecting') } await CloseWebSocket(this.socket) if (this.shouldConnect) { - throw new Error('connected before disconnected') + throw new ConnectionError('connect before disconnected') } } @@ -423,7 +498,7 @@ export default class SocketConnection extends EventEmitter { // note we can't just let socket.send fail, // have to do this check ourselves because the error appears // to be uncatchable in the browser - throw new Error('connection closed or closing') + throw new ConnectionError('connection closed or closing') } } @@ -440,18 +515,19 @@ export default class SocketConnection extends EventEmitter { return new Promise((resolve, reject) => { this.debug('>> %s', msg) // promisify send - this.socket.send(msg, (err) => { + const data = typeof msg.serialize === 'function' ? msg.serialize() : msg + this.socket.send(data, (err) => { /* istanbul ignore next */ if (err) { - reject(err) + reject(new ConnectionError(err)) return } - resolve(msg) + resolve(data) }) // send callback doesn't exist with browser websockets, just resolve /* istanbul ignore next */ if (process.isBrowser) { - resolve(msg) + resolve(data) } }) } @@ -491,3 +567,5 @@ export default class SocketConnection extends EventEmitter { return this.socket.readyState === WebSocket.CONNECTING } } + +SocketConnection.ConnectionError = ConnectionError diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js index 11a54e9f8..b459241f0 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/streams/Connection.test.js @@ -1,9 +1,12 @@ import { wait } from 'streamr-test-utils' +import Debug from 'debug' import SocketConnection from '../../../src/streams/SocketConnection' /* eslint-disable require-atomic-updates */ +const debug = Debug('StreamrClient').extend('test') + describe('SocketConnection', () => { let s let onConnected @@ -17,9 +20,10 @@ describe('SocketConnection', () => { let expectErrors = 0 // check no errors by default beforeEach(() => { + jest.setTimeout(2000) s = new SocketConnection({ url: 'wss://echo.websocket.org/', - maxRetries: 3 + maxRetries: 2 }) onConnected = jest.fn() @@ -37,6 +41,7 @@ describe('SocketConnection', () => { onMessage = jest.fn() s.on('message', onMessage) expectErrors = 0 + debug('starting test') }) afterEach(async () => { @@ -46,7 +51,12 @@ describe('SocketConnection', () => { }) afterEach(async () => { + debug('disconnecting after test') await s.disconnect() + if (SocketConnection.getOpen() !== 0) { + throw new Error('not closed') + } + await wait(1000) }) describe('basics', () => { @@ -74,7 +84,7 @@ describe('SocketConnection', () => { await s.connect() await s.connect() expect(s.isConnected()).toBeTruthy() - // only one connect event should fire + expect(onConnected).toHaveBeenCalledTimes(1) expect(onConnecting).toHaveBeenCalledTimes(1) }) @@ -85,7 +95,6 @@ describe('SocketConnection', () => { s.connect(), ]) expect(s.isConnected()).toBeTruthy() - // only one connect event should fire expect(onConnected).toHaveBeenCalledTimes(1) expect(onConnecting).toHaveBeenCalledTimes(1) }) @@ -101,7 +110,7 @@ describe('SocketConnection', () => { s.disconnect(), ]) expect(s.isDisconnected()).toBeTruthy() - // only one connect event should fire + expect(onConnected).toHaveBeenCalledTimes(1) expect(onDisconnected).toHaveBeenCalledTimes(1) expect(onDisconnecting).toHaveBeenCalledTimes(1) @@ -118,7 +127,6 @@ describe('SocketConnection', () => { expect(s.isConnected()).toBeTruthy() - // only one connect event should fire expect(onConnected).toHaveBeenCalledTimes(2) expect(onDisconnected).toHaveBeenCalledTimes(1) expect(onDisconnecting).toHaveBeenCalledTimes(0) @@ -140,11 +148,71 @@ describe('SocketConnection', () => { expect(s.socket).not.toBe(oldSocket) }) + describe('connect/disconnect inside event handlers', () => { + it('can handle disconnect on connecting event', async (done) => { + expectErrors = 1 + s.once('connecting', async () => { + await s.disconnect() + expect(s.isDisconnected()).toBeTruthy() + done() + }) + await expect(async () => { + await s.connect() + }).rejects.toThrow() + expect(s.isDisconnected()).toBeTruthy() + }) + + it('can handle disconnect on connected event', async (done) => { + expectErrors = 1 + s.once('connected', async () => { + await s.disconnect() + expect(s.isDisconnected()).toBeTruthy() + done() + }) + + await expect(async () => { + await s.connect() + }).rejects.toThrow() + expect(s.isConnected()).toBeFalsy() + }) + + it('can handle connect on disconnecting event', async (done) => { + expectErrors = 1 + s.once('disconnecting', async () => { + await s.connect() + expect(s.isConnected()).toBeTruthy() + done() + }) + await s.connect() + await expect(async () => { + await s.disconnect() + }).rejects.toThrow() + expect(s.isDisconnected()).toBeFalsy() + }) + + it('can handle connect on disconnected event', async (done) => { + expectErrors = 1 + await s.connect() + + s.once('disconnected', async () => { + await s.connect() + s.debug('connect done') + expect(s.isConnected()).toBeTruthy() + done() + }) + + await expect(async () => { + await s.disconnect() + }).rejects.toThrow() + expect(s.isConnected()).toBeFalsy() + }) + }) + it('rejects if no url', async () => { expectErrors = 1 s = new SocketConnection({ url: undefined, - maxRetries: 3, + maxRetries: 2, }) onConnected = jest.fn() s.on('connected', onConnected) @@ -160,7 +228,7 @@ describe('SocketConnection', () => { expectErrors = 1 s = new SocketConnection({ url: 'badurl', - maxRetries: 3, + maxRetries: 2, }) onConnected = jest.fn() s.on('connected', onConnected) @@ -176,7 +244,7 @@ describe('SocketConnection', () => { expectErrors = 1 s = new SocketConnection({ url: 'wss://streamr.network/nope', - maxRetries: 3, + maxRetries: 2, }) onConnected = jest.fn() s.on('connected', onConnected) @@ -326,6 +394,7 @@ describe('SocketConnection', () => { }) it('retries multiple times when disconnected', async (done) => { + s.options.maxRetries = 3 /* eslint-disable no-underscore-dangle */ await s.connect() const goodUrl = s.options.url @@ -346,7 +415,7 @@ describe('SocketConnection', () => { }) s.socket.close() /* eslint-enable no-underscore-dangle */ - }) + }, 3000) it('fails if exceed max retries', async (done) => { expectErrors = 1 From 31e5c5102c566c712f22f6f57d5aa13b65b485f0 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 3 Sep 2020 16:53:42 -0400 Subject: [PATCH 032/517] Add tests around nextConnection. --- test/unit/streams/Connection.test.js | 55 ++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js index b459241f0..35378421b 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/streams/Connection.test.js @@ -53,10 +53,10 @@ describe('SocketConnection', () => { afterEach(async () => { debug('disconnecting after test') await s.disconnect() - if (SocketConnection.getOpen() !== 0) { - throw new Error('not closed') + const openSockets = SocketConnection.getOpen() + if (openSockets !== 0) { + throw new Error(`sockets not closed: ${openSockets}`) } - await wait(1000) }) describe('basics', () => { @@ -370,6 +370,55 @@ describe('SocketConnection', () => { }) }) + describe('nextConnection', () => { + it('resolves on next connection', async () => { + let resolved = false + const next = s.nextConnection().then((v) => { + resolved = true + return v + }) + await s.connect() + expect(resolved).toBe(true) + await next + }) + + it('rejects on next error', async () => { + expectErrors = 1 + let errored = false + s.options.url = 'badurl' + const next = s.nextConnection().catch((err) => { + errored = true + throw err + }) + await expect(async () => { + await s.connect() + }).rejects.toThrow() + expect(errored).toBe(true) + await expect(async () => { + await next + }).rejects.toThrow() + }) + + it('rejects if disconnected while connecting', async () => { + expectErrors = 1 + let errored = false + const next = s.nextConnection().catch((err) => { + errored = true + throw err + }) + await Promise.all([ + expect(async () => { + await s.connect() + }).rejects.toThrow(), + s.disconnect() + ]) + expect(errored).toBe(true) + await expect(async () => { + await next + }).rejects.toThrow() + }) + }) + describe('reconnecting', () => { it('reconnects if unexpectedly disconnected', async (done) => { await s.connect() From 410b0940d7dbb1ee98729fad3e417c6bdf1725c5 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 3 Sep 2020 17:32:18 -0400 Subject: [PATCH 033/517] Disable autoConnect if manually disconnected. --- src/streams/SocketConnection.js | 1 + test/unit/streams/Connection.test.js | 24 +++++++++++++++++++++++- 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js index 6777df74f..73e366a1b 100644 --- a/src/streams/SocketConnection.js +++ b/src/streams/SocketConnection.js @@ -409,6 +409,7 @@ export default class SocketConnection extends EventEmitter { } async disconnect() { + this.options.autoConnect = false // reset auto-connect on manual disconnect this.shouldConnect = false if (this.disconnectTask) { return this.disconnectTask diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js index 35378421b..a4e2be62d 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/streams/Connection.test.js @@ -473,7 +473,10 @@ describe('SocketConnection', () => { s.options.url = 'badurl' s.once('error', (err) => { expect(err).toBeTruthy() - done() + // wait a moment for late errors + setTimeout(() => { + done() + }, 100) }) s.socket.close() }) @@ -630,6 +633,25 @@ describe('SocketConnection', () => { await s.send('test') }).rejects.toThrow() }) + + it('fails send if autoconnected but intentionally disconnected', async () => { + s.options.autoConnect = true + const received = [] + s.on('message', ({ data } = {}) => { + received.push(data) + }) + + const nextMessage = new Promise((resolve) => s.once('message', resolve)) + await s.send('test') // ok + await nextMessage + expect(received).toEqual(['test']) + await s.disconnect() // messages after this point should fail + await expect(async () => { + await s.send('test2') + }).rejects.toThrow('connection') + await wait(100) + expect(received).toEqual(['test']) + }) }) }) From 2408b38528059c9234368ef35454f631ed47c090 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 8 Sep 2020 14:57:23 -0400 Subject: [PATCH 034/517] Local tests passing for refactored connection integration. --- src/Publisher.js | 20 +- src/Resender.js | 2 +- src/StreamrClient.js | 25 +- src/Subscriber.js | 74 +- src/Subscription.js | 5 + src/streams/SocketConnection.js | 90 +- test/integration/StreamrClient.test.js | 1749 ++++++++++++++---------- test/integration/Subscription.test.js | 4 +- test/unit/StreamrClient.test.js | 57 +- test/unit/streams/Connection.test.js | 36 +- 10 files changed, 1206 insertions(+), 856 deletions(-) diff --git a/src/Publisher.js b/src/Publisher.js index 47c0d8317..3c643387c 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -5,6 +5,7 @@ import Signer from './Signer' import Stream from './rest/domain/Stream' import FailedToPublishError from './errors/FailedToPublishError' import MessageCreationUtil from './MessageCreationUtil' +import Connection from './streams/SocketConnection' function getStreamId(streamObjectOrId) { if (streamObjectOrId instanceof Stream) { @@ -40,7 +41,19 @@ export default class Publisher { } } - async publish(streamObjectOrId, data, timestamp = new Date(), partitionKey = null) { + async publish(...args) { + return this._publish(...args).catch((err) => { + this.client.debug({ publishError: err }) + if (!(err instanceof Connection.ConnectionError || err.reason instanceof Connection.ConnectionError)) { + // emit non-connection errors + this.client.emit('error', err) + } + throw err + }) + } + + async _publish(streamObjectOrId, data, timestamp = new Date(), partitionKey = null) { + this.debug('publish()') if (this.client.session.isUnauthenticated()) { throw new Error('Need to be authenticated to publish.') } @@ -53,6 +66,7 @@ export default class Publisher { this.msgCreationUtil.createStreamMessage(streamObjectOrId, data, timestampAsNumber, partitionKey), ]) + this.debug('sessionToken, streamMessage') const requestId = this.client.resender.resendUtil.generateRequestId() const request = new ControlLayer.PublishRequest({ streamMessage, @@ -61,15 +75,15 @@ export default class Publisher { }) this.debug('_requestPublish: %o', request) try { - await this.client.connection.send(request) + await this.client.send(request) } catch (err) { - this.debug('adasd: %o', err) throw new FailedToPublishError( streamId, data, err ) } + return request } getPublisherId() { diff --git a/src/Resender.js b/src/Resender.js index ef9e3916b..8609066fb 100644 --- a/src/Resender.js +++ b/src/Resender.js @@ -180,7 +180,7 @@ export default class Resender { } this.debug('_requestResend: %o', request) - await this.client.connection.send(request).catch((err) => { + await this.client.send(request).catch((err) => { this.client.handleError(`Failed to send resend request: ${err}`) }) } diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 17d3a4af4..580b4a7f0 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -90,6 +90,7 @@ export default class StreamrClient extends EventEmitter { this.session = new Session(this, this.options.auth) this.connection = connection || new Connection(this.options) this.connection.on('message', (messageEvent) => { + if (!this.connection.isConnected()) { return } // ignore messages if not connected let controlMessage try { controlMessage = ControlLayer.ControlMessage.deserialize(messageEvent.data) @@ -98,6 +99,10 @@ export default class StreamrClient extends EventEmitter { this.emit('error', err) return } + const [typeName] = Object.entries(ControlMessage.TYPES).find(([, value]) => ( + value === controlMessage.type + )) || [] + this.debug('message type', typeName) this.connection.emit(controlMessage.type, controlMessage) }) @@ -113,8 +118,6 @@ export default class StreamrClient extends EventEmitter { } async onConnectionConnected() { - await new Promise((resolve) => setTimeout(resolve, 0)) // wait a tick to let event handlers finish - if (!this.isConnected()) { return } this.debug('Connected!') this.emit('connected') } @@ -126,12 +129,11 @@ export default class StreamrClient extends EventEmitter { onConnectionError(err) { // If there is an error parsing a json message in a stream, fire error events on the relevant subs - if ((err instanceof Errors.InvalidJsonError)) { + if ((err instanceof Errors.InvalidJsonError || err.reason instanceof Errors.InvalidJsonError)) { this.subscriber.onErrorMessage(err) } else { // if it looks like an error emit as-is, otherwise wrap in new Error - const errorObject = (err && err.stack && err.message) ? err : new Error(err) - this.emit('error', errorObject) + this.emit('error', new Connection.ConnectionError(err)) } } @@ -141,9 +143,17 @@ export default class StreamrClient extends EventEmitter { } _onError(err, ...args) { - this.debug('disconnecting due to error', err) this.onError(err, ...args) - this.ensureDisconnected() + if (!(err instanceof Connection.ConnectionError)) { + this.debug('disconnecting due to error', err) + this.disconnect() + } + } + + async send(request) { + const serialized = request.serialize() + this.debug('>> %s', serialized) + return this.connection.send(request) } /** @@ -180,7 +190,6 @@ export default class StreamrClient extends EventEmitter { } async connect() { - this.debug('Connecting to %s', this.options.url) return this.connection.connect() } diff --git a/src/Subscriber.js b/src/Subscriber.js index 036341588..0fb95a363 100644 --- a/src/Subscriber.js +++ b/src/Subscriber.js @@ -45,7 +45,9 @@ export default class Subscriber { } onSubscribeResponse(response) { + if (!this.client.isConnected()) { return } const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) + this.debug('onSubscribeResponse') if (stream) { stream.setSubscribing(false) stream.getSubscriptions().filter((sub) => !sub.resending) @@ -68,6 +70,7 @@ export default class Subscriber { } async onClientConnected() { + this.debug('onClientConnected') try { if (!this.client.isConnected()) { return } // Check pending subscriptions @@ -97,7 +100,7 @@ export default class Subscriber { onErrorMessage(err) { // not ideal, see error handler in client - if (!(err instanceof Errors.InvalidJsonError)) { + if (!(err instanceof Errors.InvalidJsonError || err.reason instanceof Errors.InvalidJsonError)) { return } // If there is an error parsing a json message in a stream, fire error events on the relevant subs @@ -115,6 +118,8 @@ export default class Subscriber { // Backwards compatibility for giving an options object as third argument Object.assign(options, legacyOptions) + this.debug('subscribe', options) + if (!options.stream) { throw new Error('subscribe: Invalid arguments: options.stream is not given') } @@ -145,6 +150,9 @@ export default class Subscriber { }) } sub.on('gap', (from, to, publisherId, msgChainId) => { + this.debug('gap', { + from, to, publisherId, msgChainId + }) if (!sub.resending) { // eslint-disable-next-line no-underscore-dangle this.client.resender._requestResend(sub, { @@ -153,7 +161,7 @@ export default class Subscriber { } }) sub.on('done', () => { - this.debug('done event for sub %d', sub.id) + this.debug('done event for sub %s', sub.id) this.unsubscribe(sub) }) @@ -175,20 +183,33 @@ export default class Subscriber { throw new Error('unsubscribe: please give a Subscription object as an argument!') } - const sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) + const { streamId, streamPartition } = sub + + this.debug('unsubscribe', { + streamId, + streamPartition, + }) + + const sp = this._getSubscribedStreamPartition(streamId, streamPartition) + // If this is the last subscription for this stream-partition, unsubscribe the client too - if (sp && sp.getSubscriptions().length === 1 - && sub.getState() === Subscription.State.subscribed) { + if (sp + && sp.getSubscriptions().length === 1 + && sub.getState() === Subscription.State.subscribed + ) { + this.debug('last subscription') sub.setState(Subscription.State.unsubscribing) return this._requestUnsubscribe(sub) } if (sub.getState() !== Subscription.State.unsubscribing && sub.getState() !== Subscription.State.unsubscribed) { - // Else the sub can be cleaned off immediately + this.debug('remove sub') this._removeSubscription(sub) + // Else the sub can be cleaned off immediately sub.setState(Subscription.State.unsubscribed) return this._checkAutoDisconnect() } + return Promise.resolve() } unsubscribeAll(streamId, streamPartition) { @@ -313,12 +334,11 @@ export default class Subscriber { sessionToken, requestId: this.client.resender.resendUtil.generateRequestId(), }) - this.debug('_requestSubscribe: subscribing client: %o', request) sp.setSubscribing(true) - await this.client.connection.nextConnection() - await this.client.connection.send(request).catch((err) => { + this.debug('_requestSubscribe: subscribing client: %o', request) + await this.client.send(request).catch((err) => { sub.setState(Subscription.State.unsubscribed) - this.client.emit('error', `Failed to send subscribe request: ${err.stack}`) + this.client.emit('error', new Error(`Failed to send subscribe request: ${err.stack}`)) }) } @@ -342,6 +362,7 @@ export default class Subscriber { this.debug('Disconnecting due to no longer being subscribed to any streams') return this.client.disconnect() } + return Promise.resolve() } // eslint-disable-next-line class-methods-use-this @@ -376,20 +397,23 @@ export default class Subscriber { } sub.setState(Subscription.State.subscribing) - if (!sub.hasResendOptions()) { - return this._requestSubscribe(sub) - } - - const onSubscribed = new Promise((resolve, reject) => { - // Once subscribed, ask for a resend - sub.once('subscribed', () => { - // eslint-disable-next-line no-underscore-dangle - resolve(this.client.resender._requestResend(sub)) - }) - sub.once('error', reject) - }) - await this._requestSubscribe(sub) - - return onSubscribed + return Promise.all([ + this._requestSubscribe(sub), + // eslint-disable-next-line no-underscore-dangle + sub.hasResendOptions() && this.client.resender._requestResend(sub), + ]) + + //const onSubscribed = new Promise((resolve, reject) => { + //this.debug('add resend on sub') + //// Once subscribed, ask for a resend + //sub.once('subscribed', () => { + //// eslint-disable-next-line no-underscore-dangle + //resolve(this.client.resender._requestResend(sub)) + //}) + //sub.once('error', reject) + //}) + //await this._requestSubscribe(sub) + + //return onSubscribed } } diff --git a/src/Subscription.js b/src/Subscription.js index 11170eeaf..0f2d9ec98 100644 --- a/src/Subscription.js +++ b/src/Subscription.js @@ -41,6 +41,11 @@ export default class Subscription extends EventEmitter { this.state = Subscription.State.unsubscribed } + emit(event, ...args) { + this.debug('emit', event) + return super.emit(event, ...args) + } + getState() { return this.state } diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js index 73e366a1b..7444b1816 100644 --- a/src/streams/SocketConnection.js +++ b/src/streams/SocketConnection.js @@ -1,10 +1,11 @@ -import { PassThrough, Writable, pipeline } from 'stream' - +import { ControlLayer } from 'streamr-client-protocol' import EventEmitter from 'eventemitter3' import debugFactory from 'debug' import uniqueId from 'lodash.uniqueid' import WebSocket from 'ws' +const { ControlMessage } = ControlLayer + class ConnectionError extends Error { constructor(err, ...args) { if (err instanceof ConnectionError) { @@ -14,7 +15,9 @@ class ConnectionError extends Error { if (err && err.stack) { const { message, stack } = err super(message, ...args) + Object.assign(this, err) this.stack = stack + this.reason = err } else { super(err, ...args) if (Error.captureStackTrace) { @@ -140,7 +143,15 @@ export default class SocketConnection extends EventEmitter { this.debug('emit', event, ...args) return super.emit(event, err, ...rest) } - this.debug('emit', event) + + if (typeof event === 'number') { + const [typeName] = Object.entries(ControlMessage.TYPES).find(([, value]) => ( + value === event + )) || [] + this.debug('emit', typeName, ...args) + } else { + this.debug('emit', event) + } // note if event handler is async and it rejects we're kinda hosed // until node lands unhandledrejection support @@ -226,7 +237,7 @@ export default class SocketConnection extends EventEmitter { this._isReconnecting = false throw err } - this.debug('trying again') + this.debug('trying again...') return this.reconnect() }) })().finally(() => { @@ -248,13 +259,13 @@ export default class SocketConnection extends EventEmitter { if (this.initialConnectTask === initialConnectTask) { this.initialConnectTask = undefined } + this.debug('error while opening', err) // reconnect on initial connection failure if (!this.shouldConnect) { throw err } - this.debug('error while opening', err) this.debug = this._debug if (this.initialConnectTask === initialConnectTask) { this.initialConnectTask = undefined @@ -277,50 +288,55 @@ export default class SocketConnection extends EventEmitter { this.initialConnectTask = undefined } }) + this.debug('set initial') this.initialConnectTask = initialConnectTask return this.initialConnectTask } async _connectOnce() { const { debug } = this - if (!this.shouldConnect) { - throw new ConnectionError('disconnected before connected') - } - - if (this.isConnected()) { - debug('connect(): aleady connected') - return Promise.resolve() - } - - if (this.socket && this.socket.readyState === WebSocket.CLOSING) { - debug('waiting for close') - await CloseWebSocket(this.socket) - } - if (this.connectTask) { debug('reuse connection %s', this.socket && this.socket.readyState) return this.connectTask } - const connectTask = this._connect().then((value) => { - const debugCurrent = this.debug - const { socket } = this // capture so we can ignore if not current - socket.addEventListener('close', async () => { - // reconnect on unexpected failure - if ((this.socket && socket !== this.socket) || !this.shouldConnect) { - return - } + const connectTask = (async () => { + if (!this.shouldConnect) { + throw new ConnectionError('disconnected before connected') + } - debug('unexpected close') - // eslint-disable-next-line promise/no-nesting - await this.reconnect().catch((error) => { - debugCurrent('failed reconnect after connected', error) - this.emit('error', error) + if (this.isConnected()) { + debug('connect(): aleady connected') + return Promise.resolve() + } + + if (this.socket && this.socket.readyState === WebSocket.CLOSING) { + debug('waiting for close') + await CloseWebSocket(this.socket) + } + + return this._connect().then((value) => { + const debugCurrent = this.debug + const { socket } = this // capture so we can ignore if not current + socket.addEventListener('close', async () => { + // reconnect on unexpected failure + if ((this.socket && socket !== this.socket) || !this.shouldConnect) { + return + } + + debug('unexpected close') + // eslint-disable-next-line promise/no-nesting + await this.reconnect().catch((error) => { + debugCurrent('failed reconnect after connected', error) + this.emit('error', error) + }) }) + return value }) - return value - }).finally(() => { - this.connectTask = undefined + })().finally(() => { + if (this.connectTask === connectTask) { + this.connectTask = undefined + } }) this.connectTask = connectTask @@ -330,7 +346,8 @@ export default class SocketConnection extends EventEmitter { async _connect() { this.debug = this._debug.extend(uniqueId('socket')) const { debug } = this - debug('connect()', this.socket && this.socket.readyState) + await true // wait a tick + debug('connecting', this.socket && this.socket.readyState) this.emitTransition('connecting') if (!this.shouldConnect) { @@ -338,6 +355,7 @@ export default class SocketConnection extends EventEmitter { throw new ConnectionError('disconnected before connected') } + debug('connecting...', this.options.url) const socket = await OpenWebSocket(this.options.url) debug('connected') if (!this.shouldConnect) { diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index d975f4ade..d3bd57f23 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -1,4 +1,3 @@ -import assert from 'assert' import fs from 'fs' import path from 'path' @@ -6,766 +5,1077 @@ import fetch from 'node-fetch' import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import { wait, waitForEvent } from 'streamr-test-utils' import { ethers } from 'ethers' +import Debug from 'debug' import { uid } from '../utils' import StreamrClient from '../../src' +import SocketConnection from '../../src/streams/SocketConnection' import config from './config' +const debug = Debug('StreamrClient').extend('test') + const { StreamMessage } = MessageLayer const WebSocket = require('ws') const { SubscribeRequest, UnsubscribeRequest, ResendLastRequest } = ControlLayer -const createClient = (opts = {}) => new StreamrClient({ - auth: { - privateKey: ethers.Wallet.createRandom().privateKey, - }, - autoConnect: false, - autoDisconnect: false, - ...config.clientOptions, - ...opts, -}) - -describe('StreamrClient Connection', () => { - describe('bad config.url', () => { - it('emits error without autoconnect', async () => { - const client = createClient({ - url: 'asdasd', - autoConnect: false, - autoDisconnect: false, - }) - client.onError = jest.fn() - - await expect(() => ( - client.connect() - )).rejects.toThrow() - expect(client.onError).toHaveBeenCalledTimes(1) - }) - - it('rejects on connect without autoconnect', async () => { - const client = createClient({ - url: 'asdasd', - autoConnect: false, - autoDisconnect: false, - }) - client.onError = jest.fn() +describe('StreamrClient', () => { + let expectErrors = 0 // check no errors by default + let onError = jest.fn() + let client - await expect(() => ( - client.connect() - )).rejects.toThrow() - expect(client.onError).toHaveBeenCalledTimes(1) + const createClient = (opts = {}) => { + const c = new StreamrClient({ + auth: { + privateKey: ethers.Wallet.createRandom().privateKey, + }, + autoConnect: false, + autoDisconnect: false, + maxRetries: 2, + ...config.clientOptions, + ...opts, }) + c.onError = jest.fn() + c.on('error', onError) + return c + } - it('emits error with autoconnect after first call that triggers connect()', async () => { - const client = createClient({ - url: 'asdasd', - autoConnect: true, - autoDisconnect: true, - }) - const client2 = createClient({ - autoConnect: true, - autoDisconnect: true, - }) + async function checkConnection() { + const c = createClient() + // create a temp client before connecting ws + // so client generates correct options.url for us + try { + await Promise.all([ + Promise.race([ + fetch(c.options.restUrl), + wait(1000).then(() => { + throw new Error(`timed out connecting to: ${c.options.restUrl}`) + }) + ]), + Promise.race([ + new Promise((resolve, reject) => { + const ws = new WebSocket(c.options.url) + ws.once('open', () => { + c.debug('open', c.options.url) + resolve() + ws.close() + }) + ws.once('error', (err) => { + c.debug('err', c.options.url, err) + reject(err) + ws.terminate() + }) + }), + wait(1000).then(() => { + throw new Error(`timed out connecting to: ${c.options.url}`) + }) + ]), + ]) + } catch (e) { + if (e.errno === 'ENOTFOUND' || e.errno === 'ECONNREFUSED') { + throw new Error('Integration testing requires that engine-and-editor ' + + 'and data-api ("entire stack") are running in the background. ' + + 'Instructions: https://github.com/streamr-dev/streamr-docker-dev#running') + } else { + throw e + } + } + } - client.onError = jest.fn() - const onError = jest.fn() - client.on('error', onError) - - const stream = await client2.createStream({ - name: uid('stream') - }) // this will succeed because it uses restUrl config, not url - - // publish should trigger connect - await expect(() => ( - client.publish(stream, {}) - )).rejects.toThrow('Invalid URL') - // check error is emitted with same error before rejection - // not clear if emit or reject *should* occur first - expect(onError).toHaveBeenCalledTimes(1) - expect(client.onError).toHaveBeenCalledTimes(1) - }, 10000) + beforeEach(() => { + expectErrors = 0 + onError = jest.fn() }) - describe('bad config.restUrl', () => { - it('emits no error with no connection', async (done) => { - const client = createClient({ - restUrl: 'asdasd', - autoConnect: false, - autoDisconnect: false, - }) - client.onError = jest.fn() - client.once('error', done) - setTimeout(() => { - expect(client.onError).not.toHaveBeenCalled() - done() - }, 100) - }) + beforeAll(async () => { + await checkConnection() + }) - it('does not emit error with connect', async (done) => { - // error will come through when getting session - const client = createClient({ - restUrl: 'asdasd', - autoConnect: false, - autoDisconnect: false, - }) - client.onError = jest.fn() - client.once('error', done) - client.connect() - setTimeout(() => { - expect(client.onError).not.toHaveBeenCalled() - done() - }, 100) - }) + afterEach(async () => { + await wait() + // ensure no unexpected errors + expect(onError).toHaveBeenCalledTimes(expectErrors) + if (client) { + expect(client.onError).toHaveBeenCalledTimes(expectErrors) + } }) - it('can disconnect before connected', async () => { - const client = createClient() - client.onError = jest.fn() - client.once('error', (error) => { - expect(error).toMatchObject({ - message: 'Failed to send subscribe request: Error: WebSocket is not open: readyState 3 (CLOSED)', - }) + afterEach(async () => { + await wait() + if (client) { + client.debug('disconnecting after test') + await client.disconnect() + } - expect(client.onError).toHaveBeenCalledTimes(1) - }) - client.connect() - await client.ensureDisconnected() + const openSockets = SocketConnection.getOpen() + if (openSockets !== 0) { + throw new Error(`sockets not closed: ${openSockets}`) + } }) - describe('resend', () => { - let client - let stream + describe('Connection', () => { + describe('bad config.url', () => { + it('emits error without autoconnect', async () => { + expectErrors = 1 + client = createClient({ + url: 'asdasd', + autoConnect: false, + autoDisconnect: false, + }) - let timestamps = [] + await expect(() => ( + client.connect() + )).rejects.toThrow() + }) - beforeEach(async () => { - client = createClient() - await client.ensureConnected() + it('rejects on connect without autoconnect', async () => { + expectErrors = 1 + client = createClient({ + url: 'asdasd', + autoConnect: false, + autoDisconnect: false, + }) - stream = await client.createStream({ - name: uid('stream') + await expect(() => ( + client.connect() + )).rejects.toThrow() }) - timestamps = [] - for (let i = 0; i < 5; i++) { - const message = { - msg: `message${i}`, - } + it('emits error with autoconnect after first call that triggers connect()', async () => { + expectErrors = 1 - // eslint-disable-next-line no-await-in-loop - const rawMessage = await client.publish(stream.id, message) - timestamps.push(rawMessage.streamMessage.getTimestamp()) - // eslint-disable-next-line no-await-in-loop - await wait(100) // ensure timestamp increments for reliable resend response in test. - } + client = createClient({ + url: 'asdasd', + autoConnect: true, + autoDisconnect: true, + }) + const client2 = createClient({ + autoConnect: true, + autoDisconnect: true, + }) - await wait(5000) // wait for messages to (probably) land in storage - }, 10 * 1000) + const otherOnError = jest.fn() + client2.on('error', otherOnError) - afterEach(async () => { - await client.ensureDisconnected() + const stream = await client2.createStream({ + name: uid('stream') + }) // this will succeed because it uses restUrl config, not url + + // publish should trigger connect + await expect(() => ( + client.publish(stream, {}) + )).rejects.toThrow('Invalid URL') + // check error is emitted with same error before rejection + // not clear if emit or reject *should* occur first + expect(onError).toHaveBeenCalledTimes(1) + expect(client.onError).toHaveBeenCalledTimes(1) + expect(otherOnError).toHaveBeenCalledTimes(0) + }, 10000) }) - it('resend last', async () => { - const messages = [] + describe('bad config.restUrl', () => { + it('emits no error with no connection', async (done) => { + client = createClient({ + restUrl: 'asdasd', + autoConnect: false, + autoDisconnect: false, + }) + setTimeout(() => { + expect(client.onError).not.toHaveBeenCalled() + done() + }, 100) + }) - const sub = await client.resend({ - stream: stream.id, - resend: { - last: 3, - }, - }, (message) => { - messages.push(message) + it('does not emit error with connect', async (done) => { + // error will come through when getting session + client = createClient({ + restUrl: 'asdasd', + autoConnect: false, + autoDisconnect: false, + }) + await client.connect() + setTimeout(() => { + expect(client.onError).not.toHaveBeenCalled() + done() + }, 100) }) + }) + + describe('resend', () => { + let stream - await waitForEvent(sub, 'resent') - expect(messages).toHaveLength(3) - expect(messages).toEqual([{ - msg: 'message2', - }, { - msg: 'message3', - }, { - msg: 'message4', - }]) - }, 15000) - - it('resend from', async () => { - const messages = [] - - const sub = await client.resend( - { + let timestamps = [] + + beforeEach(async () => { + client = createClient() + await client.connect() + + stream = await client.createStream({ + name: uid('stream') + }) + + timestamps = [] + for (let i = 0; i < 5; i++) { + const message = { + msg: `message${i}`, + } + + // eslint-disable-next-line no-await-in-loop + const rawMessage = await client.publish(stream.id, message) + timestamps.push(rawMessage.streamMessage.getTimestamp()) + // eslint-disable-next-line no-await-in-loop + await wait(100) // ensure timestamp increments for reliable resend response in test. + } + + await wait(5000) // wait for messages to (probably) land in storage + }, 10 * 1000) + + it('resend last', async () => { + const messages = [] + + const sub = await client.resend({ stream: stream.id, resend: { - from: { - timestamp: timestamps[3], - }, + last: 3, }, - }, - (message) => { + }, (message) => { messages.push(message) - }, - ) + }) - await waitForEvent(sub, 'resent') - expect(messages).toEqual([ - { + await waitForEvent(sub, 'resent') + expect(messages).toHaveLength(3) + expect(messages).toEqual([{ + msg: 'message2', + }, { msg: 'message3', - }, - { + }, { msg: 'message4', - }, - ]) - }, 10000) - - it('resend range', async () => { - const messages = [] - - const sub = await client.resend( - { - stream: stream.id, - resend: { - from: { - timestamp: timestamps[0], + }]) + }, 15000) + + it('resend from', async () => { + const messages = [] + + const sub = await client.resend( + { + stream: stream.id, + resend: { + from: { + timestamp: timestamps[3], + }, }, - to: { - timestamp: timestamps[3] - 1, + }, + (message) => { + messages.push(message) + }, + ) + + await waitForEvent(sub, 'resent') + expect(messages).toEqual([ + { + msg: 'message3', + }, + { + msg: 'message4', + }, + ]) + }, 10000) + + it('resend range', async () => { + const messages = [] + + const sub = await client.resend( + { + stream: stream.id, + resend: { + from: { + timestamp: timestamps[0], + }, + to: { + timestamp: timestamps[3] - 1, + }, }, }, - }, - (message) => { - messages.push(message) - }, - ) - - await waitForEvent(sub, 'resent') - expect(messages).toEqual([ - { - msg: 'message0', - }, - { - msg: 'message1', - }, - { - msg: 'message2', - }, - ]) - }, 10000) - }) + (message) => { + messages.push(message) + }, + ) - describe('ensureConnected', () => { - it('connects the client', async () => { - const client = createClient() - await client.ensureConnected() - expect(client.isConnected()).toBeTruthy() - // no error if already connected - await client.ensureConnected() - expect(client.isConnected()).toBeTruthy() - await client.disconnect() + await waitForEvent(sub, 'resent') + expect(messages).toEqual([ + { + msg: 'message0', + }, + { + msg: 'message1', + }, + { + msg: 'message2', + }, + ]) + }, 10000) }) - it('does not error if connecting', async (done) => { - const client = createClient() - client.connection.once('connecting', async () => { - await client.ensureConnected() + describe('connect handling', () => { + it('connects the client', async () => { + client = createClient() + await client.connect() + expect(client.isConnected()).toBeTruthy() + // no error if already connected + await client.connect() expect(client.isConnected()).toBeTruthy() await client.disconnect() - done() }) - await client.connect() - }) + it('does not error if connecting', async (done) => { + client = createClient() + client.connection.once('connecting', async () => { + await client.connect() + expect(client.isConnected()).toBeTruthy() + done() + }) - it('connects if disconnecting', async (done) => { - const client = createClient() - client.connection.once('disconnecting', async () => { - await client.ensureConnected() + await client.connect() expect(client.isConnected()).toBeTruthy() - await client.disconnect() - done() }) - await client.connect() - await client.disconnect() - }) - }) - - describe('ensureDisconnected', () => { - it('disconnects the client', async () => { - const client = createClient() - // no error if already disconnected - await client.ensureDisconnected() - await client.connect() - await client.ensureDisconnected() - expect(client.isDisconnected()).toBeTruthy() - }) + it('connects if disconnecting', async (done) => { + expectErrors = 1 + client = createClient() + client.connection.once('disconnecting', async () => { + await client.connect() + expect(client.isConnected()).toBeTruthy() + await client.disconnect() + done() + }) - it('does not error if disconnecting', async (done) => { - const client = createClient() - client.connection.once('disconnecting', async () => { - await client.ensureDisconnected() - expect(client.isDisconnected()).toBeTruthy() - done() + await client.connect() + await expect(async () => { + await client.disconnect() + }).rejects.toThrow() }) - await client.connect() - await client.disconnect() }) - it('disconnects if connecting', async (done) => { - const client = createClient() - client.connection.once('connecting', async () => { - await client.ensureDisconnected() + describe('disconnect handling', () => { + it('disconnects the client', async () => { + client = createClient() + // no error if already disconnected + await client.disconnect() + await client.connect() + await client.disconnect() expect(client.isDisconnected()).toBeTruthy() - done() }) - await client.connect() - }) - - it('clear _reconnectTimeout when disconnecting client', async (done) => { - const client = createClient() - await client.ensureConnected() - client.once('disconnected', async () => { - await client.ensureDisconnected() - setTimeout(() => { + it('does not error if disconnecting', async (done) => { + client = createClient() + client.connection.once('disconnecting', async () => { + await client.disconnect() expect(client.isDisconnected()).toBeTruthy() done() - }, 2500) + }) + await client.connect() + await client.disconnect() }) - client.connection.socket.close() - }) - }) + it('disconnects if connecting', async (done) => { + expectErrors = 1 + client = createClient() + client.connection.once('connecting', async () => { + await client.disconnect() + expect(client.isDisconnected()).toBeTruthy() + done() + }) + await expect(async () => { + await client.connect() + }).rejects.toThrow() + }) - describe('connect during disconnect', () => { - let client - async function teardown() { - if (client) { - client.removeAllListeners('error') - await client.ensureDisconnected() - } - } + it('clear _reconnectTimeout when disconnecting client', async (done) => { + client = createClient() + await client.connect() - beforeEach(async () => { - await teardown() - }) + client.once('disconnected', async () => { + await client.disconnect() + setTimeout(() => { + expect(client.isDisconnected()).toBeTruthy() + done() + }, 2500) + }) - afterEach(async () => { - await teardown() + client.connection.socket.close() + }) }) - it('can reconnect after disconnect', (done) => { - client = createClient() - client.once('error', done) - client.connect() - client.once('connected', async () => { - await client.disconnect() - }) - client.once('disconnected', () => { - client.connect() + describe('connect during disconnect', () => { + it('can reconnect after disconnect', async (done) => { + expectErrors = 3 + client = createClient() client.once('connected', async () => { - await client.disconnect() - done() + await expect(async () => { + await client.disconnect() + }).rejects.toThrow() }) + client.once('disconnected', async () => { + client.once('connected', async () => { + await client.disconnect() + done() + }) + + await expect(async () => { + await client.connect() + }).rejects.toThrow() + }) + await expect(async () => { + await client.connect() + }).rejects.toThrow() }) - }) - it('can disconnect before connected', async (done) => { - client = createClient() - client.once('error', done) - client.connect() - await client.disconnect() - done() - }) + it('can disconnect before connected', async () => { + expectErrors = 1 + client = createClient() - it('can connect', async (done) => { - client = createClient() - await client.connect() + const t = expect(async () => { + await client.connect() + }).rejects.toThrow() + await client.disconnect() + await t + }) - client.connection.once('disconnecting', async () => { - await client.connect() + it('can disconnect before connected', async () => { + expectErrors = 1 + client = createClient() + const t = expect(async () => { + await client.connect() + }).rejects.toThrow() await client.disconnect() - done() + await t + expect(client.onError).toHaveBeenCalledTimes(1) }) - await client.disconnect() - }, 5000) + it('can connect', async (done) => { + expectErrors = 1 + client = createClient() + await client.connect() - it('will resolve original disconnect', async (done) => { - client = createClient() + client.connection.once('disconnecting', async () => { + await client.connect() + await client.disconnect() + done() + }) - await client.connect() + await expect(async () => { + await client.disconnect() + }).rejects.toThrow() + }, 5000) + + it('will resolve original disconnect', async () => { + expectErrors = 1 + client = createClient() - client.connection.once('disconnecting', async () => { await client.connect() - }) - await client.disconnect() - done() // ok if it ever gets here - }, 5000) - it('has connection state transitions in correct order', async (done) => { - client = createClient() - const connectionEventSpy = jest.spyOn(client.connection, 'emit') + client.connection.once('disconnecting', async () => { + await client.connect() + }) + await expect(async () => { + await client.disconnect() + }).rejects.toThrow() + }, 5000) - await client.connect() + it('has connection state transitions in correct order', async (done) => { + expectErrors = 1 + client = createClient() + const connectionEventSpy = jest.spyOn(client.connection, 'emit') - client.connection.once('disconnecting', async () => { await client.connect() - const eventNames = connectionEventSpy.mock.calls.map(([eventName]) => eventName) - expect(eventNames).toEqual([ - 'connecting', - 'connected', - 'disconnecting', - 'disconnected', // should disconnect before re-connecting - 'connecting', - 'connected', - ]) - done() - }) - await client.disconnect() - }, 5000) - - it('should not subscribe to unsubscribed streams on reconnect', async (done) => { - client = createClient() - await client.ensureConnected() - const sessionToken = await client.session.getSessionToken() - - const stream = await client.createStream({ - name: uid('stream') - }) - const connectionEventSpy = jest.spyOn(client.connection, 'send') - const sub = client.subscribe(stream.id, () => {}) + client.connection.once('disconnecting', async () => { + await client.connect() + const eventNames = connectionEventSpy.mock.calls.map(([eventName]) => eventName) + expect(eventNames).toEqual([ + 'connecting', + 'connected', + 'disconnecting', + 'error', + ]) + expect(client.isConnected()).toBeTruthy() + done() + }) - sub.once('subscribed', async () => { - await wait(100) - client.unsubscribe(sub) - }) + await expect(async () => { + await client.disconnect() + }).rejects.toThrow() + }, 5000) - sub.once('unsubscribed', async () => { - await client.ensureDisconnected() - await client.ensureConnected() - await client.ensureDisconnected() - - // check whole list of calls after reconnect and disconnect - expect(connectionEventSpy.mock.calls[0]).toEqual([new SubscribeRequest({ - streamId: stream.id, - streamPartition: 0, - sessionToken, - requestId: connectionEventSpy.mock.calls[0][0].requestId, - })]) - - expect(connectionEventSpy.mock.calls[1]).toEqual([new UnsubscribeRequest({ - streamId: stream.id, - streamPartition: 0, - sessionToken, - requestId: connectionEventSpy.mock.calls[1][0].requestId, - })]) - - // key exchange stream subscription should not have been sent yet - expect(connectionEventSpy.mock.calls.length).toEqual(2) - done() - }) - }) + it('should not subscribe to unsubscribed streams on reconnect', async (done) => { + client = createClient() + await client.connect() + const sessionToken = await client.session.getSessionToken() - it('should not subscribe after resend() on reconnect', async (done) => { - client = createClient() - await client.ensureConnected() - const sessionToken = await client.session.getSessionToken() + const stream = await client.createStream({ + name: uid('stream') + }) - const stream = await client.createStream({ - name: uid('stream') - }) + const connectionEventSpy = jest.spyOn(client.connection, '_send') + const sub = client.subscribe(stream.id, () => {}) - const connectionEventSpy = jest.spyOn(client.connection, 'send') - const sub = await client.resend({ - stream: stream.id, - resend: { - last: 10 - } - }, () => {}) + sub.once('subscribed', async () => { + await wait(100) + await client.unsubscribe(sub) + }) - sub.once('initial_resend_done', () => { - setTimeout(async () => { - await client.pause() // simulates a disconnection at the websocket level, not on the client level. - await client.ensureConnected() - await client.ensureDisconnected() + sub.once('unsubscribed', async () => { + await client.disconnect() + await client.connect() + await client.disconnect() + // key exchange stream subscription should not have been sent yet + expect(connectionEventSpy.mock.calls).toHaveLength(2) // check whole list of calls after reconnect and disconnect - expect(connectionEventSpy.mock.calls[0]).toEqual([new ResendLastRequest({ + expect(connectionEventSpy.mock.calls[0]).toEqual([new SubscribeRequest({ streamId: stream.id, streamPartition: 0, sessionToken, - numberLast: 10, requestId: connectionEventSpy.mock.calls[0][0].requestId, })]) - // key exchange stream subscription should not have been sent yet - expect(connectionEventSpy.mock.calls.length).toEqual(1) + expect(connectionEventSpy.mock.calls[1]).toEqual([new UnsubscribeRequest({ + streamId: stream.id, + streamPartition: 0, + sessionToken, + requestId: connectionEventSpy.mock.calls[1][0].requestId, + })]) + done() - }, 2000) + }) }) - }, 5000) - it('does not try to reconnect', async (done) => { - client = createClient() - client.once('error', done) - await client.connect() - - client.connection.once('disconnecting', async () => { + it('should not subscribe after resend() on reconnect', async (done) => { + client = createClient() await client.connect() + const sessionToken = await client.session.getSessionToken() - // should not try connecting after disconnect (or any other reason) - const onConnecting = () => { - done(new Error('should not be connecting')) - } - client.once('connecting', onConnecting) + const stream = await client.createStream({ + name: uid('stream') + }) - await client.disconnect() - // wait for possible reconnections - setTimeout(() => { - client.off('connecting', onConnecting) - expect(client.isConnected()).toBe(false) - done() - }, 2000) - }) - await client.disconnect() - }, 6000) - }) + const connectionEventSpy = jest.spyOn(client.connection, 'send') + const sub = await client.resend({ + stream: stream.id, + resend: { + last: 10 + } + }, () => {}) - describe('publish/subscribe connection handling', () => { - let client - async function teardown() { - if (!client) { return } - client.removeAllListeners('error') - await client.ensureDisconnected() - client = undefined - } + sub.once('initial_resend_done', () => { + setTimeout(async () => { + await client.pause() // simulates a disconnection at the websocket level, not on the client level. + await client.connect() + await client.disconnect() - beforeEach(async () => { - await teardown() - }) + // check whole list of calls after reconnect and disconnect + expect(connectionEventSpy.mock.calls[0]).toEqual([new ResendLastRequest({ + streamId: stream.id, + streamPartition: 0, + sessionToken, + numberLast: 10, + requestId: connectionEventSpy.mock.calls[0][0].requestId, + })]) + + // key exchange stream subscription should not have been sent yet + expect(connectionEventSpy.mock.calls.length).toEqual(1) + done() + }, 2000) + }) + }, 5000) - afterEach(async () => { - await teardown() - }) + it('does not try to reconnect', async (done) => { + expectErrors = 1 + client = createClient() + await client.connect() - describe('publish', () => { - it('will connect if not connected if autoconnect set', async (done) => { - client = createClient({ - autoConnect: true, - autoDisconnect: true, + client.connection.once('disconnecting', async () => { + await client.connect() + + // should not try connecting after disconnect (or any other reason) + const onConnecting = () => { + done(new Error('should not be connecting')) + } + client.once('connecting', onConnecting) + + await client.disconnect() + // wait for possible reconnections + setTimeout(() => { + client.off('connecting', onConnecting) + expect(client.isConnected()).toBe(false) + done() + }, 2000) }) + await expect(async () => { + await client.disconnect() + }).rejects.toThrow() + }, 6000) + }) - client.once('error', done) + describe('publish/subscribe connection handling', () => { + describe('publish', () => { + it('will connect if not connected if autoconnect set', async (done) => { + client = createClient({ + autoConnect: true, + autoDisconnect: true, + }) - const stream = await client.createStream({ - name: uid('stream') + const stream = await client.createStream({ + name: uid('stream') + }) + expect(client.isDisconnected()).toBeTruthy() + + const message = { + id2: uid('msg') + } + client.once('connected', () => { + // wait in case of delayed errors + setTimeout(() => done(), 500) + }) + await client.publish(stream.id, message) }) - await client.ensureDisconnected() - const message = { - id2: uid('msg') - } - client.once('connected', () => { + it('errors if disconnected autoconnect set', async (done) => { + expectErrors = 0 // publish error doesn't cause error events + client = createClient({ + autoConnect: true, + autoDisconnect: true, + }) + + await client.connect() + const stream = await client.createStream({ + name: uid('stream') + }) + + const message = { + id1: uid('msg') + } + const p = client.publish(stream.id, message) + await wait() + await client.disconnect() // start async disconnect after publish started + await expect(p).rejects.toThrow() + expect(client.isDisconnected()).toBeTruthy() // wait in case of delayed errors setTimeout(() => done(), 500) }) - await client.publish(stream.id, message) - }) - it('will connect if disconnecting & autoconnect set', async (done) => { - client = createClient({ - autoConnect: true, - autoDisconnect: true, - }) + it('errors if disconnected autoconnect not set', async (done) => { + expectErrors = 0 + client = createClient({ + autoConnect: false, + autoDisconnect: true, + }) - client.once('error', done) - await client.ensureConnected() - const stream = await client.createStream({ - name: uid('stream') - }) + await client.connect() + const stream = await client.createStream({ + name: uid('stream') + }) - const message = { - id1: uid('msg') - } - const p = client.publish(stream.id, message) - setTimeout(async () => { + const message = { + id1: uid('msg') + } + const p = client.publish(stream.id, message) + await wait() await client.disconnect() // start async disconnect after publish started + await expect(p).rejects.toThrow() + expect(client.isDisconnected()).toBeTruthy() + // wait in case of delayed errors + setTimeout(() => done(), 500) }) - await p - // wait in case of delayed errors - setTimeout(() => done(), 500) }) - it('will error if disconnecting & autoconnect not set', async (done) => { - client = createClient({ - autoConnect: false, - autoDisconnect: false, - }) + describe('subscribe', () => { + it('does not error if disconnect after subscribe', async (done) => { + client = createClient({ + autoConnect: true, + autoDisconnect: true, + }) - client.onError = jest.fn() - client.once('error', done) - await client.ensureConnected() - const stream = await client.createStream({ - name: uid('stream') - }) + await client.connect() + const stream = await client.createStream({ + name: uid('stream') + }) - const message = { - id1: uid('msg') - } + const sub = client.subscribe({ + stream: stream.id, + }, () => {}) - client.publish(stream.id, message).catch((err) => { - expect(err).toBeTruthy() - setTimeout(() => { + sub.once('subscribed', async () => { + await client.disconnect() // wait in case of delayed errors - expect(client.onError).not.toHaveBeenCalled() - done() + setTimeout(() => { + expect(client.onError).not.toHaveBeenCalled() + done() + }, 100) }) - }) // don't wait - - setTimeout(() => { - client.disconnect() // start async disconnect after publish started - }) - }) - }) - describe('subscribe', () => { - it('does not error if disconnect after subscribe', async (done) => { - client = createClient({ - autoConnect: true, - autoDisconnect: true, }) - client.onError = jest.fn() - client.once('error', done) - await client.ensureConnected() - const stream = await client.createStream({ - name: uid('stream') - }) + it('does not error if disconnect after subscribe with resend', async (done) => { + client = createClient({ + autoConnect: true, + autoDisconnect: true, + }) - const sub = client.subscribe({ - stream: stream.id, - resend: { - from: { - timestamp: 0, + await client.connect() + const stream = await client.createStream({ + name: uid('stream') + }) + + const sub = client.subscribe({ + stream: stream.id, + resend: { + from: { + timestamp: 0, + }, }, - }, - }, () => {}) - sub.once('subscribed', async () => { - await client.disconnect() - // wait in case of delayed errors - setTimeout(() => { - expect(client.onError).not.toHaveBeenCalled() - done() - }, 100) + }, () => {}) + + sub.once('subscribed', async () => { + await client.disconnect() + // wait in case of delayed errors + setTimeout(() => { + expect(client.onError).not.toHaveBeenCalled() + done() + }, 100) + }) }) }) }) }) -}) -describe('StreamrClient', () => { - let client - let stream + describe('StreamrClient', () => { + let stream + + // These tests will take time, especially on Travis + const TIMEOUT = 5 * 1000 + + const createStream = async () => { + const name = `StreamrClient-integration-${Date.now()}` + expect(client.isConnected()).toBeTruthy() - // These tests will take time, especially on Travis - const TIMEOUT = 5 * 1000 + const s = await client.createStream({ + name, + requireSignedData: true, + }) - const createStream = async () => { - const name = `StreamrClient-integration-${Date.now()}` - assert(client.isConnected()) + expect(s.id).toBeTruthy() + expect(s.name).toEqual(name) + expect(s.requireSignedData).toBe(true) + return s + } - const s = await client.createStream({ - name, - requireSignedData: true, + beforeEach(async () => { + client = createClient() + await client.connect() + stream = await createStream() + const publisherId = await client.getPublisherId() + const res = await client.isStreamPublisher(stream.id, publisherId.toLowerCase()) + expect(res).toBe(true) + expect(onError).toHaveBeenCalledTimes(0) }) - assert(s.id) - assert.equal(s.name, name) - assert.strictEqual(s.requireSignedData, true) - return s - } + afterEach(async () => { + await wait() + // ensure no unexpected errors + expect(onError).toHaveBeenCalledTimes(expectErrors) + }) - beforeEach(async () => { - client = createClient() - // create client before connecting ws - // so client generates correct options.url for us + afterEach(async () => { + await wait() - try { - await Promise.all([ - fetch(config.clientOptions.restUrl), - new Promise((resolve, reject) => { - const ws = new WebSocket(client.options.url) - ws.once('open', () => { - resolve() - ws.close() - }) - ws.once('error', (err) => { - reject(err) - ws.terminate() - }) - }), - ]) - } catch (e) { - if (e.errno === 'ENOTFOUND' || e.errno === 'ECONNREFUSED') { - throw new Error('Integration testing requires that engine-and-editor ' - + 'and data-api ("entire stack") are running in the background. ' - + 'Instructions: https://github.com/streamr-dev/streamr-docker-dev#running') - } else { - throw e + if (client) { + client.debug('disconnecting after test') + await client.disconnect() } - } - await client.ensureConnected() - stream = await createStream() - const publisherId = await client.getPublisherId() - const res = await client.isStreamPublisher(stream.id, publisherId.toLowerCase()) - assert.strictEqual(res, true) - }) - afterEach(async () => { - if (client) { - client.removeAllListeners('error') - await client.ensureDisconnected() - } - }) + const openSockets = SocketConnection.getOpen() + if (openSockets !== 0) { + throw new Error(`sockets not closed: ${openSockets}`) + } + }) - describe('Pub/Sub', () => { - it('client.publish', async (done) => { - client.once('error', done) - await client.publish(stream.id, { - test: 'client.publish', - }) - setTimeout(() => done(), TIMEOUT * 0.8) - }, TIMEOUT) + describe('Pub/Sub', () => { + it('client.publish does not error', async (done) => { + await client.publish(stream.id, { + test: 'client.publish', + }) + setTimeout(() => done(), TIMEOUT * 0.5) + }, TIMEOUT) - it('Stream.publish', async (done) => { - client.once('error', done) - await stream.publish({ - test: 'Stream.publish', - }) - setTimeout(() => done(), TIMEOUT * 0.8) - }, TIMEOUT) + it('Stream.publish does not error', async (done) => { + await stream.publish({ + test: 'Stream.publish', + }) + setTimeout(() => done(), TIMEOUT * 0.5) + }, TIMEOUT) - it('client.publish with Stream object as arg', async (done) => { - client.once('error', done) - await client.publish(stream, { - test: 'client.publish.Stream.object', + it('client.publish with Stream object as arg', async (done) => { + await client.publish(stream, { + test: 'client.publish.Stream.object', + }) + setTimeout(() => done(), TIMEOUT * 0.5) + }, TIMEOUT) + + describe('subscribe/unsubscribe', () => { + it('client.subscribe then unsubscribe after subscribed without resend', async () => { + expect(client.getSubscriptions(stream.id)).toHaveLength(0) + + const sub = client.subscribe({ + stream: stream.id, + }, () => {}) + + const onSubscribed = jest.fn() + sub.on('subscribed', onSubscribed) + const onUnsubscribed = jest.fn() + sub.on('unsubscribed', onUnsubscribed) + + expect(client.getSubscriptions(stream.id)).toHaveLength(1) // has subscription immediately + await new Promise((resolve) => sub.once('subscribed', resolve)) + expect(client.getSubscriptions(stream.id)).toHaveLength(1) + const t = new Promise((resolve) => sub.once('unsubscribed', resolve)) + await client.unsubscribe(sub) + await t + expect(client.getSubscriptions(stream.id)).toHaveLength(0) + expect(onSubscribed).toHaveBeenCalledTimes(1) + expect(onUnsubscribed).toHaveBeenCalledTimes(1) + }, TIMEOUT) + + it('client.subscribe then unsubscribe before subscribed without resend', async () => { + expect(client.getSubscriptions(stream.id)).toHaveLength(0) + + const sub = client.subscribe({ + stream: stream.id, + }, () => {}) + + expect(client.getSubscriptions(stream.id)).toHaveLength(1) + const onSubscribed = jest.fn() + sub.on('subscribed', onSubscribed) + const onUnsubscribed = jest.fn() + sub.on('unsubscribed', onUnsubscribed) + const t = client.unsubscribe(sub) + expect(client.getSubscriptions(stream.id)).toHaveLength(0) // lost subscription immediately + await t + await wait(TIMEOUT * 0.2) + expect(onSubscribed).toHaveBeenCalledTimes(0) + expect(onUnsubscribed).toHaveBeenCalledTimes(1) + }, TIMEOUT) + + it('client.subscribe then unsubscribe before subscribed with resend', async () => { + expect(client.getSubscriptions(stream.id)).toHaveLength(0) + + const sub = client.subscribe({ + stream: stream.id, + resend: { + from: { + timestamp: 0, + }, + }, + }, () => {}) + + expect(client.getSubscriptions(stream.id)).toHaveLength(1) + const onSubscribed = jest.fn() + sub.on('subscribed', onSubscribed) + const onUnsubscribed = jest.fn() + sub.on('unsubscribed', onUnsubscribed) + const t = client.unsubscribe(sub) + expect(client.getSubscriptions(stream.id)).toHaveLength(0) // lost subscription immediately + await t + await wait(TIMEOUT * 0.2) + expect(onSubscribed).toHaveBeenCalledTimes(0) + expect(onUnsubscribed).toHaveBeenCalledTimes(1) + }, TIMEOUT) + + it('client.subscribe then unsubscribe before subscribed with resend', async () => { + expect(client.getSubscriptions(stream.id)).toHaveLength(0) + + const sub = client.subscribe({ + stream: stream.id, + resend: { + from: { + timestamp: 0, + }, + }, + }, () => {}) + + expect(client.getSubscriptions(stream.id)).toHaveLength(1) + const onSubscribed = jest.fn() + sub.on('subscribed', onSubscribed) + const onResent = jest.fn() + sub.on('resent', onResent) + const onNoResend = jest.fn() + sub.on('no_resend', onNoResend) + const onUnsubscribed = jest.fn() + sub.on('unsubscribed', onUnsubscribed) + const t = client.unsubscribe(sub) + expect(client.getSubscriptions(stream.id)).toHaveLength(0) // lost subscription immediately + await t + await wait(TIMEOUT * 0.2) + expect(onResent).toHaveBeenCalledTimes(0) + expect(onNoResend).toHaveBeenCalledTimes(0) + expect(onSubscribed).toHaveBeenCalledTimes(0) + expect(onUnsubscribed).toHaveBeenCalledTimes(1) + }, TIMEOUT) + + it('client.subscribe then unsubscribe ignores messages', async () => { + expect(client.getSubscriptions(stream.id)).toHaveLength(0) + + const onMessage = jest.fn() + const sub = client.subscribe({ + stream: stream.id, + }, onMessage) + + expect(client.getSubscriptions(stream.id)).toHaveLength(1) + const onSubscribed = jest.fn() + sub.on('subscribed', onSubscribed) + const onResent = jest.fn() + sub.on('resent', onResent) + const onNoResend = jest.fn() + sub.on('no_resend', onNoResend) + const onUnsubscribed = jest.fn() + sub.on('unsubscribed', onUnsubscribed) + await new Promise((resolve) => sub.once('subscribed', resolve)) + const msg = { + name: uid('msg') + } + const t = client.unsubscribe(sub) + await stream.publish(msg) + await t + expect(client.getSubscriptions(stream.id)).toHaveLength(0) // lost subscription immediately + await wait(TIMEOUT * 0.2) + expect(onResent).toHaveBeenCalledTimes(0) + expect(onMessage).toHaveBeenCalledTimes(0) + expect(onNoResend).toHaveBeenCalledTimes(0) + expect(onSubscribed).toHaveBeenCalledTimes(1) + expect(onUnsubscribed).toHaveBeenCalledTimes(1) + }, TIMEOUT) + + it('client.subscribe then unsubscribe ignores messages with resend', async () => { + const msg = { + name: uid('msg') + } + await stream.publish(msg) + + await wait(TIMEOUT * 0.5) + const onMessage = jest.fn() + const sub = client.subscribe({ + stream: stream.id, + resend: { + from: { + timestamp: 0, + }, + }, + }, onMessage) + + expect(client.getSubscriptions(stream.id)).toHaveLength(1) + const onSubscribed = jest.fn() + sub.on('subscribed', onSubscribed) + const onResent = jest.fn() + sub.on('resent', onResent) + const onNoResend = jest.fn() + sub.on('no_resend', onNoResend) + const onUnsubscribed = jest.fn() + sub.on('unsubscribed', onUnsubscribed) + client.debug(1) + await new Promise((resolve) => sub.once('subscribed', resolve)) + client.debug(2) + const t = new Promise((resolve) => sub.once('unsubscribed', resolve)) + await client.unsubscribe(sub) + client.debug(3) + await t + client.debug(4) + expect(client.getSubscriptions(stream.id)).toHaveLength(0) // lost subscription immediately + expect(onResent).toHaveBeenCalledTimes(0) + expect(onMessage).toHaveBeenCalledTimes(0) + expect(onNoResend).toHaveBeenCalledTimes(0) + expect(onSubscribed).toHaveBeenCalledTimes(1) + expect(onUnsubscribed).toHaveBeenCalledTimes(1) + }, TIMEOUT * 2) }) - setTimeout(() => done(), TIMEOUT * 0.8) - }, TIMEOUT) - - it('client.subscribe with resend from', (done) => { - client.once('error', done) - // Publish message - client.publish(stream.id, { - test: 'client.subscribe with resend', + + it('client.subscribe (realtime)', async (done) => { + const id = Date.now() + const sub = client.subscribe({ + stream: stream.id, + }, async (parsedContent, streamMessage) => { + expect(parsedContent.id).toBe(id) + + // Check signature stuff + expect(streamMessage.signatureType).toBe(StreamMessage.SIGNATURE_TYPES.ETH) + expect(streamMessage.getPublisherId()).toBeTruthy() + expect(streamMessage.signature).toBeTruthy() + + // All good, unsubscribe + await client.unsubscribe(sub) + done() + }) + + // Publish after subscribed + sub.once('subscribed', () => { + stream.publish({ + id, + }) + }) }) - // Check that we're not subscribed yet - assert.strictEqual(client.getSubscriptions()[stream.id], undefined) + it('publish and subscribe a sequence of messages', async (done) => { + client.options.autoConnect = true + const nbMessages = 3 + const intervalMs = 100 + let counter = 0 + const sub = client.subscribe({ + stream: stream.id, + }, async (parsedContent, streamMessage) => { + expect(parsedContent.i).toBe(counter) + counter += 1 + + // Check signature stuff + expect(streamMessage.signatureType).toBe(StreamMessage.SIGNATURE_TYPES.ETH) + expect(streamMessage.getPublisherId()).toBeTruthy() + expect(streamMessage.signature).toBeTruthy() + + client.debug({ + parsedContent, + counter, + nbMessages, + }) + if (counter === nbMessages) { + // All good, unsubscribe + await client.unsubscribe(sub) + await client.disconnect() + await wait(1000) + done() + } + }) + + // Publish after subscribed + await new Promise((resolve) => sub.once('subscribed', resolve)) + for (let i = 0; i < nbMessages; i++) { + // eslint-disable-next-line no-await-in-loop + await wait(intervalMs) + // eslint-disable-next-line no-await-in-loop + await stream.publish({ + i, + }) + } + }, 20000) + + it('client.subscribe with resend from', async (done) => { + // Publish message + await client.publish(stream.id, { + test: 'client.subscribe with resend', + }) + + // Check that we're not subscribed yet + expect(client.getSubscriptions()[stream.id]).toBe(undefined) - // Add delay: this test needs some time to allow the message to be written to Cassandra - setTimeout(() => { + // Add delay: this test needs some time to allow the message to be written to Cassandra + await wait(TIMEOUT * 0.8) const sub = client.subscribe({ stream: stream.id, resend: { @@ -775,7 +1085,7 @@ describe('StreamrClient', () => { }, }, async (parsedContent, streamMessage) => { // Check message content - assert.strictEqual(parsedContent.test, 'client.subscribe with resend') + expect(parsedContent.test).toBe('client.subscribe with resend') // Check signature stuff // WARNING: digging into internals @@ -783,33 +1093,32 @@ describe('StreamrClient', () => { const publishers = await subStream.getPublishers() const map = {} map[client.publisher.signer.address.toLowerCase()] = true - assert.deepStrictEqual(publishers, map) - assert.strictEqual(streamMessage.signatureType, StreamMessage.SIGNATURE_TYPES.ETH) - assert(streamMessage.getPublisherId()) - assert(streamMessage.signature) + expect(publishers).toEqual(map) + expect(streamMessage.signatureType).toBe(StreamMessage.SIGNATURE_TYPES.ETH) + expect(streamMessage.getPublisherId()).toBeTruthy() + expect(streamMessage.signature).toBeTruthy() // All good, unsubscribe - client.unsubscribe(sub) - sub.once('unsubscribed', () => { - assert.strictEqual(client.getSubscriptions(stream.id).length, 0) - done() - }) + const t = new Promise((resolve) => sub.once('unsubscribed', resolve)) + await client.unsubscribe(sub) + await t + expect(client.getSubscriptions(stream.id)).toHaveLength(0) + done() }) - }, TIMEOUT * 0.8) - }, TIMEOUT) + }, TIMEOUT) - it('client.subscribe with resend last', (done) => { - client.once('error', done) - // Publish message - client.publish(stream.id, { - test: 'client.subscribe with resend', - }) + it('client.subscribe with resend last', async (done) => { + // Publish message + await client.publish(stream.id, { + test: 'client.subscribe with resend', + }) + + // Check that we're not subscribed yet + expect(client.getSubscriptions(stream.id)).toHaveLength(0) - // Check that we're not subscribed yet - assert.strictEqual(client.getSubscriptions(stream.id).length, 0) + // Add delay: this test needs some time to allow the message to be written to Cassandra + await wait(TIMEOUT * 0.7) - // Add delay: this test needs some time to allow the message to be written to Cassandra - setTimeout(() => { const sub = client.subscribe({ stream: stream.id, resend: { @@ -817,7 +1126,7 @@ describe('StreamrClient', () => { }, }, async (parsedContent, streamMessage) => { // Check message content - assert.strictEqual(parsedContent.test, 'client.subscribe with resend') + expect(parsedContent.test).toEqual('client.subscribe with resend') // Check signature stuff // WARNING: digging into internals @@ -825,158 +1134,78 @@ describe('StreamrClient', () => { const publishers = await subStream.getPublishers() const map = {} map[client.publisher.signer.address.toLowerCase()] = true - assert.deepStrictEqual(publishers, map) - assert.strictEqual(streamMessage.signatureType, StreamMessage.SIGNATURE_TYPES.ETH) - assert(streamMessage.getPublisherId()) - assert(streamMessage.signature) + expect(publishers).toEqual(map) + expect(streamMessage.signatureType).toBe(StreamMessage.SIGNATURE_TYPES.ETH) + expect(streamMessage.getPublisherId()).toBeTruthy() + expect(streamMessage.signature).toBeTruthy() // All good, unsubscribe - client.unsubscribe(sub) - sub.once('unsubscribed', () => { - assert.strictEqual(client.getSubscriptions(stream.id).length, 0) - done() - }) - }) - }, TIMEOUT * 0.8) - }, TIMEOUT) - - it('client.subscribe (realtime)', (done) => { - client.once('error', done) - const id = Date.now() - const sub = client.subscribe({ - stream: stream.id, - }, (parsedContent, streamMessage) => { - assert.equal(parsedContent.id, id) - - // Check signature stuff - assert.strictEqual(streamMessage.signatureType, StreamMessage.SIGNATURE_TYPES.ETH) - assert(streamMessage.getPublisherId()) - assert(streamMessage.signature) - - // All good, unsubscribe - client.unsubscribe(sub) - sub.once('unsubscribed', () => { + const t = new Promise((resolve) => sub.once('unsubscribed', resolve)) + await client.unsubscribe(sub) + await t + expect(client.getSubscriptions(stream.id)).toHaveLength(0) done() }) - }) + }, TIMEOUT) - // Publish after subscribed - sub.once('subscribed', () => { - stream.publish({ - id, - }) - }) - }) + it('client.subscribe (realtime with resend)', (done) => { + const id = Date.now() + const sub = client.subscribe({ + stream: stream.id, + resend: { + last: 1, + }, + }, async (parsedContent, streamMessage) => { + expect(parsedContent.id).toBe(id) + + // Check signature stuff + expect(streamMessage.signatureType).toBe(StreamMessage.SIGNATURE_TYPES.ETH) + expect(streamMessage.getPublisherId()).toBeTruthy() + expect(streamMessage.signature).toBeTruthy() - it('publish and subscribe a sequence of messages', (done) => { - client.options.autoConnect = true - const nbMessages = 20 - const intervalMs = 500 - let counter = 1 - const sub = client.subscribe({ - stream: stream.id, - }, (parsedContent, streamMessage) => { - assert.strictEqual(parsedContent.i, counter) - counter += 1 - - // Check signature stuff - assert.strictEqual(streamMessage.signatureType, StreamMessage.SIGNATURE_TYPES.ETH) - assert(streamMessage.getPublisherId()) - assert(streamMessage.signature) - - if (counter === nbMessages) { // All good, unsubscribe - client.unsubscribe(sub) - sub.once('unsubscribed', async () => { - await client.disconnect() - setTimeout(done, 1000) - }) - } - }) + await client.unsubscribe(sub) + done() + }) - const sleep = (ms) => { - return new Promise((resolve) => setTimeout(resolve, ms)) - } - const f = async (index) => { - await sleep(intervalMs) - await stream.publish({ - i: index, + // Publish after subscribed + sub.once('subscribed', () => { + stream.publish({ + id, + }) }) + }, 30000) + }) + + describe('utf-8 encoding', () => { + const publishedMessage = { + content: fs.readFileSync(path.join(__dirname, 'utf8Example.txt'), 'utf8') } - // Publish after subscribed - sub.once('subscribed', () => { - let i - const loop = async () => { - for (i = 1; i <= nbMessages; i++) { - await f(i) // eslint-disable-line no-await-in-loop - } - } - return loop() - }) - }, 20000) - - it('client.subscribe (realtime with resend)', (done) => { - client.once('error', done) - - const id = Date.now() - const sub = client.subscribe({ - stream: stream.id, - resend: { - last: 1, - }, - }, (parsedContent, streamMessage) => { - assert.equal(parsedContent.id, id) - - // Check signature stuff - assert.strictEqual(streamMessage.signatureType, StreamMessage.SIGNATURE_TYPES.ETH) - assert(streamMessage.getPublisherId()) - assert(streamMessage.signature) - - sub.once('unsubscribed', () => { + it('decodes realtime messages correctly', async (done) => { + client.once('error', done) + client.subscribe(stream.id, (msg) => { + expect(msg).toStrictEqual(publishedMessage) done() + }).once('subscribed', () => { + client.publish(stream.id, publishedMessage) }) - // All good, unsubscribe - client.unsubscribe(sub) }) - // Publish after subscribed - sub.once('subscribed', () => { - stream.publish({ - id, + it('decodes resent messages correctly', async (done) => { + client.once('error', done) + await client.publish(stream.id, publishedMessage) + await wait(5000) + await client.resend({ + stream: stream.id, + resend: { + last: 3, + }, + }, (msg) => { + expect(msg).toStrictEqual(publishedMessage) + done() }) - }) - }, 30000) - }) - - describe('utf-8 encoding', () => { - const publishedMessage = { - content: fs.readFileSync(path.join(__dirname, 'utf8Example.txt'), 'utf8') - } - - it('decodes realtime messages correctly', async (done) => { - client.once('error', done) - client.subscribe(stream.id, (msg) => { - expect(msg).toStrictEqual(publishedMessage) - done() - }).once('subscribed', () => { - client.publish(stream.id, publishedMessage) - }) + }, 10000) }) - - it('decodes resent messages correctly', async (done) => { - client.once('error', done) - await client.publish(stream.id, publishedMessage) - await wait(5000) - await client.resend({ - stream: stream.id, - resend: { - last: 3, - }, - }, (msg) => { - expect(msg).toStrictEqual(publishedMessage) - done() - }) - }, 10000) }) }) diff --git a/test/integration/Subscription.test.js b/test/integration/Subscription.test.js index 2c4209898..7482dff1e 100644 --- a/test/integration/Subscription.test.js +++ b/test/integration/Subscription.test.js @@ -48,9 +48,9 @@ describe('Subscription', () => { stream = undefined } - if (client && client.isConnected()) { - await client.disconnect() + if (client) { client.off('error', throwError) + await client.disconnect() client = undefined } } diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index db664aa57..323b77efd 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -173,13 +173,20 @@ describe('StreamrClient', () => { client.on('error', onError) }) - afterEach(async () => { + afterEach(() => { client.removeListener('error', onError) - await client.ensureDisconnected() expect(errors[0]).toBeFalsy() expect(errors).toHaveLength(0) }) + afterEach(async () => { + await client.disconnect() + const openSockets = Connection.getOpen() + if (openSockets !== 0) { + throw new Error(`sockets not closed: ${openSockets}`) + } + }) + afterAll(async () => { await wait(3000) // give tests a few more moments to clean up }) @@ -235,21 +242,33 @@ describe('StreamrClient', () => { }) describe('disconnection behaviour', () => { - beforeEach(async () => client.ensureConnected()) + beforeEach(async () => client.connect()) it('emits disconnected event on client', async (done) => { client.once('disconnected', done) await connection.disconnect() }) - it('does not remove subscriptions', async () => { + it('removes subscriptions', async () => { + const sub = mockSubscription('stream1', () => {}) + await new Promise((resolve) => sub.once('subscribed', resolve)) + await client.disconnect() + expect(client.getSubscriptions(sub.streamId)).toEqual([]) + }) + + it('does not remove subscriptions if disconnected accidentally', async () => { const sub = client.subscribe('stream1', () => {}) - await connection.disconnect() + client.connection.socket.close() + await new Promise((resolve) => client.once('disconnected', resolve)) + expect(client.getSubscriptions(sub.streamId)).toEqual([sub]) + expect(sub.getState()).toEqual(Subscription.State.unsubscribed) + await client.connect() expect(client.getSubscriptions(sub.streamId)).toEqual([sub]) }) it('sets subscription state to unsubscribed', async () => { - const sub = client.subscribe('stream1', () => {}) + const sub = mockSubscription('stream1', () => {}) + await new Promise((resolve) => sub.once('subscribed', resolve)) await connection.disconnect() expect(sub.getState()).toEqual(Subscription.State.unsubscribed) }) @@ -286,7 +305,8 @@ describe('StreamrClient', () => { }, () => {}) sub.once('subscribed', async () => { await wait(100) - const { requestId } = requests[requests.length - 1] + const { requestId, type } = requests[requests.length - 1] + expect(type).toEqual(ControlMessage.TYPES.ResendLastRequest) const streamMessage = getStreamMessage(sub.streamId, {}) connection.emitMessage(new UnicastMessage({ requestId, @@ -742,7 +762,7 @@ describe('StreamrClient', () => { ) sub.handleError = async (err) => { - expect(err).toBe(jsonError) + expect(err && err.message).toMatch(jsonError.message) done() } connection.emit('error', jsonError) @@ -753,7 +773,7 @@ describe('StreamrClient', () => { const testError = new Error('This is a test error message, ignore') client.once('error', async (err) => { - expect(err).toBe(testError) + expect(err.message).toMatch(testError.message) expect(client.onError).toHaveBeenCalled() done() }) @@ -1388,28 +1408,25 @@ describe('StreamrClient', () => { describe('disconnect()', () => { beforeEach(() => client.connect()) - it('calls connection.disconnect()', (done) => { - connection.disconnect = () => done() - client.disconnect() - }) - - it('resets subscriptions', async () => { - const sub = mockSubscription('stream1', () => {}) + it('calls connection.disconnect()', async () => { + const disconnect = jest.spyOn(connection, 'disconnect') await client.disconnect() - expect(client.getSubscriptions(sub.streamId)).toEqual([]) + expect(disconnect).toHaveBeenCalledTimes(1) }) }) describe('pause()', () => { beforeEach(() => client.connect()) - it('calls connection.disconnect()', (done) => { - connection.disconnect = done - client.pause() + it('calls connection.disconnect()', async () => { + const disconnect = jest.spyOn(connection, 'disconnect') + await client.pause() + expect(disconnect).toHaveBeenCalledTimes(1) }) it('does not reset subscriptions', async () => { const sub = mockSubscription('stream1', () => {}) + await new Promise((resolve) => sub.once('subscribed', resolve)) await client.pause() expect(client.getSubscriptions(sub.streamId)).toEqual([sub]) }) diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js index a4e2be62d..08c4f34ad 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/streams/Connection.test.js @@ -149,6 +149,18 @@ describe('SocketConnection', () => { }) describe('connect/disconnect inside event handlers', () => { + it('can handle connect on connecting event', async (done) => { + s.once('connecting', async () => { + await s.connect() + expect(s.isConnected()).toBeTruthy() + expect(onConnected).toHaveBeenCalledTimes(1) + expect(onConnecting).toHaveBeenCalledTimes(1) + done() + }) + await s.connect() + expect(s.isConnected()).toBeTruthy() + }) + it('can handle disconnect on connecting event', async (done) => { expectErrors = 1 s.once('connecting', async () => { @@ -176,6 +188,28 @@ describe('SocketConnection', () => { expect(s.isConnected()).toBeFalsy() }) + it('can handle disconnect on connected event, repeated', async (done) => { + expectErrors = 3 + s.once('connected', async () => { + await expect(async () => { + await s.disconnect() + }).rejects.toThrow() + }) + s.once('disconnected', async () => { + s.once('connected', async () => { + await s.disconnect() + done() + }) + + await expect(async () => { + await s.connect() + }).rejects.toThrow() + }) + await expect(async () => { + await s.connect() + }).rejects.toThrow() + }) + it('can handle connect on disconnecting event', async (done) => { expectErrors = 1 s.once('disconnecting', async () => { @@ -515,7 +549,7 @@ describe('SocketConnection', () => { s.connect(), s.disconnect(), ]) - )).rejects.toThrow('badurl') + )).rejects.toThrow('disconnected before connected') s.options.url = goodUrl await s.connect() expect(s.isConnected()).toBeTruthy() From f72dd48df21307e98153c555e319e8544577b195 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 8 Sep 2020 15:10:27 -0400 Subject: [PATCH 035/517] Lint, tidy up. --- src/Publisher.js | 4 +++- src/StreamrClient.js | 2 +- src/Subscriber.js | 24 ++++++++++----------- test/unit/RealTimeSubscription.test.js | 1 - test/unit/StreamrClient.test.js | 30 +++++--------------------- 5 files changed, 21 insertions(+), 40 deletions(-) diff --git a/src/Publisher.js b/src/Publisher.js index 3c643387c..582ba0cbf 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -43,7 +43,9 @@ export default class Publisher { async publish(...args) { return this._publish(...args).catch((err) => { - this.client.debug({ publishError: err }) + this.client.debug({ + publishError: err + }) if (!(err instanceof Connection.ConnectionError || err.reason instanceof Connection.ConnectionError)) { // emit non-connection errors this.client.emit('error', err) diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 580b4a7f0..b1c81355a 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -7,7 +7,7 @@ import uniqueId from 'lodash.uniqueid' import Connection from './streams/SocketConnection' import Session from './Session' -import { waitFor, getVersionString } from './utils' +import { getVersionString } from './utils' import Publisher from './Publisher' import Resender from './Resender' import Subscriber from './Subscriber' diff --git a/src/Subscriber.js b/src/Subscriber.js index 0fb95a363..e9d22bb59 100644 --- a/src/Subscriber.js +++ b/src/Subscriber.js @@ -403,17 +403,17 @@ export default class Subscriber { sub.hasResendOptions() && this.client.resender._requestResend(sub), ]) - //const onSubscribed = new Promise((resolve, reject) => { - //this.debug('add resend on sub') - //// Once subscribed, ask for a resend - //sub.once('subscribed', () => { - //// eslint-disable-next-line no-underscore-dangle - //resolve(this.client.resender._requestResend(sub)) - //}) - //sub.once('error', reject) - //}) - //await this._requestSubscribe(sub) - - //return onSubscribed + // const onSubscribed = new Promise((resolve, reject) => { + // this.debug('add resend on sub') + /// / Once subscribed, ask for a resend + // sub.once('subscribed', () => { + /// / eslint-disable-next-line no-underscore-dangle + // resolve(this.client.resender._requestResend(sub)) + // }) + // sub.once('error', reject) + // }) + // await this._requestSubscribe(sub) + + // return onSubscribed } } diff --git a/test/unit/RealTimeSubscription.test.js b/test/unit/RealTimeSubscription.test.js index 6df6f0a8b..c303dbd4f 100644 --- a/test/unit/RealTimeSubscription.test.js +++ b/test/unit/RealTimeSubscription.test.js @@ -3,7 +3,6 @@ import { wait } from 'streamr-test-utils' import RealTimeSubscription from '../../src/RealTimeSubscription' import Subscription from '../../src/Subscription' -import AbstractSubscription from '../../src/AbstractSubscription' const { StreamMessage, MessageIDStrict, MessageRef } = MessageLayer diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index 323b77efd..ccd6effaa 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -1,6 +1,4 @@ -import EventEmitter from 'eventemitter3' import sinon from 'sinon' -import debug from 'debug' import { Wallet } from 'ethers' import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' @@ -34,33 +32,15 @@ const { } = ControlLayer const { StreamMessage, MessageRef, MessageID, MessageIDStrict } = MessageLayer -const mockDebug = debug('mock') describe('StreamrClient', () => { let client let connection - let asyncs = [] let requests = [] const streamPartition = 0 const sessionToken = 'session-token' - function async(func) { - const me = setTimeout(() => { - expect(me).toEqual(asyncs[0]) - asyncs.shift() - func() - }, 0) - asyncs.push(me) - } - - function clearAsync() { - asyncs.forEach((it) => { - clearTimeout(it) - }) - asyncs = [] - } - function setupSubscription( streamId, emitSubscribed = true, subscribeOptions = {}, handler = sinon.stub(), expectSubscribeRequest = !client.getSubscriptions(streamId).length, @@ -154,7 +134,8 @@ describe('StreamrClient', () => { const STORAGE_DELAY = 2000 beforeEach(() => { - clearAsync() + errors = [] + requests = [] connection = createConnectionMock() client = new StubbedStreamrClient({ autoConnect: false, @@ -168,8 +149,6 @@ describe('StreamrClient', () => { }, connection) connection.options = client.options - errors = [] - requests = [] client.on('error', onError) }) @@ -1299,7 +1278,9 @@ describe('StreamrClient', () => { describe('publish', () => { function getPublishRequest(content, streamId, timestamp, seqNum, prevMsgRef, requestId) { - const messageId = new MessageID(streamId, 0, timestamp, seqNum, StubbedStreamrClient.hashedUsername, client.publisher.msgCreationUtil.msgChainId) + const { hashedUsername } = StubbedStreamrClient + const { msgChainId } = client.publisher.msgCreationUtil + const messageId = new MessageID(streamId, 0, timestamp, seqNum, hashedUsername, msgChainId) const streamMessage = new StreamMessage({ messageId, prevMsgRef, @@ -1485,4 +1466,3 @@ describe('StreamrClient', () => { }) }) }) - From e18de18996619579f57b3ebe2a6e18243a77744a Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 8 Sep 2020 16:10:41 -0400 Subject: [PATCH 036/517] Increase Connection test timeout. --- test/unit/streams/Connection.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/unit/streams/Connection.test.js b/test/unit/streams/Connection.test.js index 08c4f34ad..0536aa843 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/streams/Connection.test.js @@ -20,7 +20,6 @@ describe('SocketConnection', () => { let expectErrors = 0 // check no errors by default beforeEach(() => { - jest.setTimeout(2000) s = new SocketConnection({ url: 'wss://echo.websocket.org/', maxRetries: 2 From 9493b6d6f35e425769400f4c0ab587bae9728df1 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 8 Sep 2020 17:08:15 -0400 Subject: [PATCH 037/517] Fix browser tests. --- src/streams/SocketConnection.js | 22 +++++++++++++++++++++- test/browser/browser.html | 8 ++++---- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/src/streams/SocketConnection.js b/src/streams/SocketConnection.js index 7444b1816..b0107c66f 100644 --- a/src/streams/SocketConnection.js +++ b/src/streams/SocketConnection.js @@ -521,6 +521,26 @@ export default class SocketConnection extends EventEmitter { } } + getState() { + if (this.isConnected()) { + return 'connected' + } + + if (this.isDisconnected()) { + return 'disconnected' + } + + if (this.isConnecting()) { + return 'connecting' + } + + if (this.isDisconnecting()) { + return 'disconnecting' + } + + return 'unknown' + } + async send(msg) { this.debug('send()') if (!this.isConnected()) { @@ -545,7 +565,7 @@ export default class SocketConnection extends EventEmitter { }) // send callback doesn't exist with browser websockets, just resolve /* istanbul ignore next */ - if (process.isBrowser) { + if (process.browser) { resolve(data) } }) diff --git a/test/browser/browser.html b/test/browser/browser.html index 0c6d5f156..93e811065 100644 --- a/test/browser/browser.html +++ b/test/browser/browser.html @@ -42,8 +42,8 @@ $('#connect').on('click', async () => { resetResults() - await client.ensureConnected() - $('#result').html(client.connection.state) + await client.connect() + $('#result').html(client.connection.getState()) }) $('#create').on('click', () => { @@ -100,8 +100,8 @@ }) $('#disconnect').on('click', async () => { - await client.ensureDisconnected() - $('#result').html(client.connection.state) + await client.disconnect() + $('#result').html(client.connection.getState()) }) client.on('error', (err) => { From 4d0b2d016eada34bb626a80b9b6875e3f8140c6f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 10 Sep 2020 10:36:21 -0400 Subject: [PATCH 038/517] Move Connection into src. --- src/{streams/SocketConnection.js => Connection.js} | 6 +++--- src/Publisher.js | 2 +- src/StreamrClient.js | 2 +- test/integration/StreamrClient.test.js | 6 +++--- test/unit/{streams => }/Connection.test.js | 14 +++++++------- test/unit/StreamrClient.test.js | 2 +- 6 files changed, 16 insertions(+), 16 deletions(-) rename src/{streams/SocketConnection.js => Connection.js} (99%) rename test/unit/{streams => }/Connection.test.js (98%) diff --git a/src/streams/SocketConnection.js b/src/Connection.js similarity index 99% rename from src/streams/SocketConnection.js rename to src/Connection.js index b0107c66f..0e04acf5d 100644 --- a/src/streams/SocketConnection.js +++ b/src/Connection.js @@ -98,7 +98,7 @@ async function CloseWebSocket(socket) { * waits for pending close/open before continuing */ -export default class SocketConnection extends EventEmitter { +export default class Connection extends EventEmitter { static getOpen() { return openSockets } @@ -113,7 +113,7 @@ export default class SocketConnection extends EventEmitter { this.shouldConnect = false this.retryCount = 1 this._isReconnecting = false - const id = uniqueId('SocketConnection') + const id = uniqueId('Connection') /* istanbul ignore next */ if (options.debug) { this._debug = options.debug.extend(id) @@ -607,4 +607,4 @@ export default class SocketConnection extends EventEmitter { } } -SocketConnection.ConnectionError = ConnectionError +Connection.ConnectionError = ConnectionError diff --git a/src/Publisher.js b/src/Publisher.js index 582ba0cbf..54cf972b7 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -5,7 +5,7 @@ import Signer from './Signer' import Stream from './rest/domain/Stream' import FailedToPublishError from './errors/FailedToPublishError' import MessageCreationUtil from './MessageCreationUtil' -import Connection from './streams/SocketConnection' +import Connection from './Connection' function getStreamId(streamObjectOrId) { if (streamObjectOrId instanceof Stream) { diff --git a/src/StreamrClient.js b/src/StreamrClient.js index b1c81355a..e62c3f4b4 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -5,7 +5,7 @@ import { Wallet } from 'ethers' import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' import uniqueId from 'lodash.uniqueid' -import Connection from './streams/SocketConnection' +import Connection from './Connection' import Session from './Session' import { getVersionString } from './utils' import Publisher from './Publisher' diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index d3bd57f23..c6bcf04fe 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -9,7 +9,7 @@ import Debug from 'debug' import { uid } from '../utils' import StreamrClient from '../../src' -import SocketConnection from '../../src/streams/SocketConnection' +import Connection from '../../src/Connection' import config from './config' @@ -108,7 +108,7 @@ describe('StreamrClient', () => { await client.disconnect() } - const openSockets = SocketConnection.getOpen() + const openSockets = Connection.getOpen() if (openSockets !== 0) { throw new Error(`sockets not closed: ${openSockets}`) } @@ -794,7 +794,7 @@ describe('StreamrClient', () => { await client.disconnect() } - const openSockets = SocketConnection.getOpen() + const openSockets = Connection.getOpen() if (openSockets !== 0) { throw new Error(`sockets not closed: ${openSockets}`) } diff --git a/test/unit/streams/Connection.test.js b/test/unit/Connection.test.js similarity index 98% rename from test/unit/streams/Connection.test.js rename to test/unit/Connection.test.js index 0536aa843..7ecca9271 100644 --- a/test/unit/streams/Connection.test.js +++ b/test/unit/Connection.test.js @@ -1,13 +1,13 @@ import { wait } from 'streamr-test-utils' import Debug from 'debug' -import SocketConnection from '../../../src/streams/SocketConnection' +import Connection from '../../src/Connection' /* eslint-disable require-atomic-updates */ const debug = Debug('StreamrClient').extend('test') -describe('SocketConnection', () => { +describe('Connection', () => { let s let onConnected let onConnecting @@ -20,7 +20,7 @@ describe('SocketConnection', () => { let expectErrors = 0 // check no errors by default beforeEach(() => { - s = new SocketConnection({ + s = new Connection({ url: 'wss://echo.websocket.org/', maxRetries: 2 }) @@ -52,7 +52,7 @@ describe('SocketConnection', () => { afterEach(async () => { debug('disconnecting after test') await s.disconnect() - const openSockets = SocketConnection.getOpen() + const openSockets = Connection.getOpen() if (openSockets !== 0) { throw new Error(`sockets not closed: ${openSockets}`) } @@ -243,7 +243,7 @@ describe('SocketConnection', () => { it('rejects if no url', async () => { expectErrors = 1 - s = new SocketConnection({ + s = new Connection({ url: undefined, maxRetries: 2, }) @@ -259,7 +259,7 @@ describe('SocketConnection', () => { it('rejects if bad url', async () => { expectErrors = 1 - s = new SocketConnection({ + s = new Connection({ url: 'badurl', maxRetries: 2, }) @@ -275,7 +275,7 @@ describe('SocketConnection', () => { it('rejects if cannot connect', async () => { expectErrors = 1 - s = new SocketConnection({ + s = new Connection({ url: 'wss://streamr.network/nope', maxRetries: 2, }) diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index ccd6effaa..442f206d1 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -5,7 +5,7 @@ import { wait } from 'streamr-test-utils' import FailedToPublishError from '../../src/errors/FailedToPublishError' import Subscription from '../../src/Subscription' -import Connection from '../../src/streams/SocketConnection' +import Connection from '../../src/Connection' // import StreamrClient from '../../src/StreamrClient' import { uid } from '../utils' From 02364c5aed4e95a9336220101a2d23a3b4e0aaab Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Sat, 12 Sep 2020 11:10:59 -0400 Subject: [PATCH 039/517] Tidy up debug logging. --- src/Connection.js | 34 +++++++++++----------------------- src/Publisher.js | 9 +++------ src/StreamrClient.js | 8 ++------ src/Subscriber.js | 3 +-- 4 files changed, 17 insertions(+), 37 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index 0e04acf5d..ec54d5647 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -144,12 +144,8 @@ export default class Connection extends EventEmitter { return super.emit(event, err, ...rest) } - if (typeof event === 'number') { - const [typeName] = Object.entries(ControlMessage.TYPES).find(([, value]) => ( - value === event - )) || [] - this.debug('emit', typeName, ...args) - } else { + if (event !== 'message' && typeof event !== 'number') { + // don't log for messages this.debug('emit', event) } @@ -288,15 +284,12 @@ export default class Connection extends EventEmitter { this.initialConnectTask = undefined } }) - this.debug('set initial') this.initialConnectTask = initialConnectTask return this.initialConnectTask } async _connectOnce() { - const { debug } = this if (this.connectTask) { - debug('reuse connection %s', this.socket && this.socket.readyState) return this.connectTask } @@ -306,17 +299,17 @@ export default class Connection extends EventEmitter { } if (this.isConnected()) { - debug('connect(): aleady connected') return Promise.resolve() } + const debug = this._debug.extend('connect') if (this.socket && this.socket.readyState === WebSocket.CLOSING) { - debug('waiting for close') + debug('waiting for close...') await CloseWebSocket(this.socket) + debug('closed') } return this._connect().then((value) => { - const debugCurrent = this.debug const { socket } = this // capture so we can ignore if not current socket.addEventListener('close', async () => { // reconnect on unexpected failure @@ -327,7 +320,7 @@ export default class Connection extends EventEmitter { debug('unexpected close') // eslint-disable-next-line promise/no-nesting await this.reconnect().catch((error) => { - debugCurrent('failed reconnect after connected', error) + this.debug('failed reconnect after connected', error) this.emit('error', error) }) }) @@ -347,7 +340,7 @@ export default class Connection extends EventEmitter { this.debug = this._debug.extend(uniqueId('socket')) const { debug } = this await true // wait a tick - debug('connecting', this.socket && this.socket.readyState) + debug('connecting...', this.options.url) this.emitTransition('connecting') if (!this.shouldConnect) { @@ -355,7 +348,6 @@ export default class Connection extends EventEmitter { throw new ConnectionError('disconnected before connected') } - debug('connecting...', this.options.url) const socket = await OpenWebSocket(this.options.url) debug('connected') if (!this.shouldConnect) { @@ -368,7 +360,6 @@ export default class Connection extends EventEmitter { socket.addEventListener('message', (messageEvent, ...args) => { if (this.socket !== socket) { return } - debug('<< %s', messageEvent && messageEvent.data) this.emit('message', messageEvent, ...args) }) @@ -395,7 +386,7 @@ export default class Connection extends EventEmitter { }) socket.addEventListener('error', (err) => { - debug('error', this.socket !== socket, err) + debug('error', err) if (this.socket !== socket) { return } const error = new ConnectionError(err.error || err) if (!this._isReconnecting) { @@ -432,7 +423,6 @@ export default class Connection extends EventEmitter { if (this.disconnectTask) { return this.disconnectTask } - this.debug('disconnect()') const disconnectTask = this._disconnect() .catch((err) => { this.emit('error', err) @@ -447,7 +437,7 @@ export default class Connection extends EventEmitter { } async _disconnect() { - this.debug('_disconnect()') + this.debug('disconnect()') if (this.connectTask) { try { await this.connectTask @@ -503,7 +493,6 @@ export default class Connection extends EventEmitter { } async maybeConnect() { - this.debug('maybeConnect', this.options.autoConnect, this.shouldConnect) if (this.options.autoConnect || this.shouldConnect) { // should be open, so wait for open or trigger new open await this.connect() @@ -511,13 +500,12 @@ export default class Connection extends EventEmitter { } async needsConnection() { - this.debug('needsConnection') await this.maybeConnect() if (!this.isConnected()) { // note we can't just let socket.send fail, // have to do this check ourselves because the error appears // to be uncatchable in the browser - throw new ConnectionError('connection closed or closing') + throw new ConnectionError('needs connection but connection closed or closing') } } @@ -547,12 +535,12 @@ export default class Connection extends EventEmitter { // shortcut await if connected await this.needsConnection() } + this.debug('>> %o', msg) return this._send(msg) } async _send(msg) { return new Promise((resolve, reject) => { - this.debug('>> %s', msg) // promisify send const data = typeof msg.serialize === 'function' ? msg.serialize() : msg this.socket.send(data, (err) => { diff --git a/src/Publisher.js b/src/Publisher.js index 54cf972b7..cc0f6151a 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -42,20 +42,19 @@ export default class Publisher { } async publish(...args) { + this.debug('publish()') return this._publish(...args).catch((err) => { - this.client.debug({ - publishError: err - }) if (!(err instanceof Connection.ConnectionError || err.reason instanceof Connection.ConnectionError)) { // emit non-connection errors this.client.emit('error', err) + } else { + this.debug(err) } throw err }) } async _publish(streamObjectOrId, data, timestamp = new Date(), partitionKey = null) { - this.debug('publish()') if (this.client.session.isUnauthenticated()) { throw new Error('Need to be authenticated to publish.') } @@ -68,14 +67,12 @@ export default class Publisher { this.msgCreationUtil.createStreamMessage(streamObjectOrId, data, timestampAsNumber, partitionKey), ]) - this.debug('sessionToken, streamMessage') const requestId = this.client.resender.resendUtil.generateRequestId() const request = new ControlLayer.PublishRequest({ streamMessage, requestId, sessionToken, }) - this.debug('_requestPublish: %o', request) try { await this.client.send(request) } catch (err) { diff --git a/src/StreamrClient.js b/src/StreamrClient.js index e62c3f4b4..d8962741f 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -95,14 +95,12 @@ export default class StreamrClient extends EventEmitter { try { controlMessage = ControlLayer.ControlMessage.deserialize(messageEvent.data) } catch (err) { + this.connection.debug('<< %o', messageEvent && messageEvent.data) this.debug('deserialize error', err) this.emit('error', err) return } - const [typeName] = Object.entries(ControlMessage.TYPES).find(([, value]) => ( - value === controlMessage.type - )) || [] - this.debug('message type', typeName) + this.connection.debug('<< %o', controlMessage) this.connection.emit(controlMessage.type, controlMessage) }) @@ -151,8 +149,6 @@ export default class StreamrClient extends EventEmitter { } async send(request) { - const serialized = request.serialize() - this.debug('>> %s', serialized) return this.connection.send(request) } diff --git a/src/Subscriber.js b/src/Subscriber.js index e9d22bb59..652bdb2e2 100644 --- a/src/Subscriber.js +++ b/src/Subscriber.js @@ -46,8 +46,8 @@ export default class Subscriber { onSubscribeResponse(response) { if (!this.client.isConnected()) { return } - const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) this.debug('onSubscribeResponse') + const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) if (stream) { stream.setSubscribing(false) stream.getSubscriptions().filter((sub) => !sub.resending) @@ -70,7 +70,6 @@ export default class Subscriber { } async onClientConnected() { - this.debug('onClientConnected') try { if (!this.client.isConnected()) { return } // Check pending subscriptions From ab97d9744b0428cb17564fa6588d06f21f53fea3 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 14 Sep 2020 15:10:37 -0400 Subject: [PATCH 040/517] Remove unused from Connection. --- src/Connection.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index ec54d5647..a2a71c4af 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -1,11 +1,8 @@ -import { ControlLayer } from 'streamr-client-protocol' import EventEmitter from 'eventemitter3' import debugFactory from 'debug' import uniqueId from 'lodash.uniqueid' import WebSocket from 'ws' -const { ControlMessage } = ControlLayer - class ConnectionError extends Error { constructor(err, ...args) { if (err instanceof ConnectionError) { From 290a91342693bb90f8c165f4e5b969c754f827c6 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 15 Sep 2020 11:06:50 -0400 Subject: [PATCH 041/517] Tune subscriber logging. --- src/Subscriber.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/Subscriber.js b/src/Subscriber.js index 652bdb2e2..473f64208 100644 --- a/src/Subscriber.js +++ b/src/Subscriber.js @@ -117,7 +117,7 @@ export default class Subscriber { // Backwards compatibility for giving an options object as third argument Object.assign(options, legacyOptions) - this.debug('subscribe', options) + this.debug('subscribe %o', options) if (!options.stream) { throw new Error('subscribe: Invalid arguments: options.stream is not given') @@ -149,8 +149,15 @@ export default class Subscriber { }) } sub.on('gap', (from, to, publisherId, msgChainId) => { - this.debug('gap', { - from, to, publisherId, msgChainId + this.debug('gap %o %o', { + id: sub.id, + streamId: sub.streamId, + streamPartition: sub.streamPartition, + }, { + from, + to, + publisherId, + msgChainId, }) if (!sub.resending) { // eslint-disable-next-line no-underscore-dangle @@ -182,12 +189,14 @@ export default class Subscriber { throw new Error('unsubscribe: please give a Subscription object as an argument!') } - const { streamId, streamPartition } = sub - - this.debug('unsubscribe', { + const { streamId, streamPartition, id } = sub + const info = { + id, streamId, streamPartition, - }) + } + + this.debug('unsubscribe %o', info) const sp = this._getSubscribedStreamPartition(streamId, streamPartition) @@ -196,13 +205,13 @@ export default class Subscriber { && sp.getSubscriptions().length === 1 && sub.getState() === Subscription.State.subscribed ) { - this.debug('last subscription') + this.debug('last subscription %o', info) sub.setState(Subscription.State.unsubscribing) return this._requestUnsubscribe(sub) } if (sub.getState() !== Subscription.State.unsubscribing && sub.getState() !== Subscription.State.unsubscribed) { - this.debug('remove sub') + this.debug('remove subcription %o', info) this._removeSubscription(sub) // Else the sub can be cleaned off immediately sub.setState(Subscription.State.unsubscribed) From 85035a465bd54475cd1c13de170acc522637ed86 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 15 Sep 2020 12:44:41 -0400 Subject: [PATCH 042/517] Fix default retry options when options object gets rewritten e.g. in test --- src/Connection.js | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index a2a71c4af..1f08e1b8e 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -1,8 +1,11 @@ import EventEmitter from 'eventemitter3' -import debugFactory from 'debug' +import Debug from 'debug' import uniqueId from 'lodash.uniqueid' import WebSocket from 'ws' +// add global support for pretty millisecond formatting with %n +Debug.formatters.n = (v) => Debug.humanize(v) + class ConnectionError extends Error { constructor(err, ...args) { if (err instanceof ConnectionError) { @@ -88,6 +91,8 @@ async function CloseWebSocket(socket) { }) } +const DEFAULT_MAX_RETRIES = 10 + /** * Wraps WebSocket open/close with promise methods * adds events @@ -104,9 +109,6 @@ export default class Connection extends EventEmitter { super() this.options = options this.options.autoConnect = !!this.options.autoConnect - this.options.maxRetries = this.options.maxRetries != null ? this.options.maxRetries : 10 - this.options.retryBackoffFactor = this.options.retryBackoffFactor != null ? this.options.retryBackoffFactor : 1.2 - this.options.maxRetryWait = this.options.maxRetryWait != null ? this.options.maxRetryWait : 10000 this.shouldConnect = false this.retryCount = 1 this._isReconnecting = false @@ -115,19 +117,26 @@ export default class Connection extends EventEmitter { if (options.debug) { this._debug = options.debug.extend(id) } else { - this._debug = debugFactory(`StreamrClient::${id}`) + this._debug = Debug(`StreamrClient::${id}`) } this.debug = this._debug } async backoffWait() { + const { retryBackoffFactor = 1.2, maxRetryWait = 10000 } = this.options return new Promise((resolve) => { clearTimeout(this._backoffTimeout) const timeout = Math.min( - this.options.maxRetryWait, // max wait time - Math.round((this.retryCount * 10) ** this.options.retryBackoffFactor) + maxRetryWait, // max wait time + Math.round((this.retryCount * 10) ** retryBackoffFactor) ) - this.debug('waiting %sms', timeout) + if (!timeout) { + this.debug({ + retryCount: this.retryCount, + options: this.options, + }) + } + this.debug('waiting %n', timeout) this._backoffTimeout = setTimeout(resolve, timeout) }) } @@ -173,13 +182,14 @@ export default class Connection extends EventEmitter { return result } - async reconnect(...args) { + async reconnect() { + const { maxRetries = DEFAULT_MAX_RETRIES } = this.options if (this.reconnectTask) { return this.reconnectTask } const reconnectTask = (async () => { - if (this.retryCount > this.options.maxRetries) { + if (this.retryCount > maxRetries) { // no more retries this._isReconnecting = false return Promise.resolve() @@ -208,11 +218,10 @@ export default class Connection extends EventEmitter { } const { retryCount } = this - const { maxRetries } = this.options // try again this.debug('attempting to reconnect %s of %s', retryCount, maxRetries) this.emitTransition('reconnecting') - return this._connectOnce(...args).then((value) => { + return this._connectOnce().then((value) => { this.debug('reconnect %s of %s successful', retryCount, maxRetries) // reset retry state this.reconnectTask = undefined @@ -224,7 +233,7 @@ export default class Connection extends EventEmitter { this.debug = this._debug this.reconnectTask = undefined this.retryCount += 1 - if (this.retryCount > this.options.maxRetries) { + if (this.retryCount > maxRetries) { this.debug('no more retries') // no more retries this._isReconnecting = false From a8a50a3c3ba0d1224eeba3166a13ce3e82d7eb19 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 15 Sep 2020 13:47:14 -0400 Subject: [PATCH 043/517] Fail resend request if error sending. --- src/Resender.js | 10 +++---- test/unit/StreamrClient.test.js | 47 ++++++++++++++------------------- 2 files changed, 23 insertions(+), 34 deletions(-) diff --git a/src/Resender.js b/src/Resender.js index 8609066fb..c884b5af8 100644 --- a/src/Resender.js +++ b/src/Resender.js @@ -137,10 +137,9 @@ export default class Resender { return sub } - async _requestResend(sub, resendOptions) { + async _requestResend(sub, options = sub.getResendOptions()) { sub.setResending(true) const requestId = this.resendUtil.registerResendRequestForSub(sub) - const options = resendOptions || sub.getResendOptions() const sessionToken = await this.client.session.getSessionToken() let request if (options.last > 0) { @@ -175,13 +174,10 @@ export default class Resender { } if (!request) { - this.client.handleError("Can't _requestResend without resendOptions") - return + throw new Error("Can't _requestResend without resend options") } this.debug('_requestResend: %o', request) - await this.client.send(request).catch((err) => { - this.client.handleError(`Failed to send resend request: ${err}`) - }) + await this.client.send(request) } } diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index 442f206d1..8dbf65bc1 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -777,40 +777,33 @@ describe('StreamrClient', () => { }) async function mockResend(...opts) { - let sub - connection._send = jest.fn(async (request) => { - requests.push(request) - await wait() - if (request.type === ControlMessage.TYPES.SubscribeRequest) { - connection.emitMessage(new SubscribeResponse({ - streamId: sub.streamId, - requestId: request.requestId, - streamPartition, - })) - } - - if (request.type === ControlMessage.TYPES.UnsubscribeRequest) { - connection.emitMessage(new UnsubscribeResponse({ - streamId: sub.streamId, - requestId: request.requestId, - streamPartition, - })) - } - }) - sub = await client.resend(...opts) + const sub = await client.resend(...opts) sub.on('error', onError) return sub } - it('should not send SubscribeRequest on reconnection', async () => { + it('should reject if cannot send', async () => { + client.options.autoConnect = false + await expect(async () => { + await mockResend({ + stream: 'stream1', + resend: { + last: 10 + } + }, () => {}) + }).rejects.toThrow() + }) + + it('should not send SubscribeRequest/ResendRequest on reconnection', async () => { await mockResend({ stream: 'stream1', resend: { last: 10 } }, () => {}) - await client.pause() - await client.connect() + client.connection.socket.close() + await client.nextConnection() + client.debug(connection._send.mock.calls) expect(connection._send.mock.calls.filter(([arg]) => arg.type === ControlMessage.TYPES.SubscribeRequest)).toHaveLength(0) }) @@ -829,8 +822,8 @@ describe('StreamrClient', () => { requestId, }) connection.emitMessage(resendResponse) - await client.pause() - await client.connect() + client.connection.socket.close() + await client.nextConnection() expect(connection._send.mock.calls.filter(([arg]) => arg.type === ControlMessage.TYPES.SubscribeRequest)).toHaveLength(0) }) @@ -854,7 +847,7 @@ describe('StreamrClient', () => { requestId, }) connection.emitMessage(resendResponse) - await client.pause() + client.connection.socket.close() await client.connect() expect(requests.filter((req) => req.type === ControlMessage.TYPES.SubscribeRequest)).toHaveLength(0) }) From 6f102a750280d5db9a0b3021e4fe1f3a73c4397b Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 15 Sep 2020 13:48:04 -0400 Subject: [PATCH 044/517] Adjust connection error handling to be more consistent about emit/throw. --- src/Connection.js | 58 +++++++++++++++++++++--------------- test/unit/Connection.test.js | 2 +- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index 1f08e1b8e..75792bf57 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -120,6 +120,8 @@ export default class Connection extends EventEmitter { this._debug = Debug(`StreamrClient::${id}`) } this.debug = this._debug + this.onConnectError = this.onConnectError.bind(this) + this.onDisconnectError = this.onDisconnectError.bind(this) } async backoffWait() { @@ -251,6 +253,21 @@ export default class Connection extends EventEmitter { return this.reconnectTask } + onConnectError(error) { + const err = new ConnectionError(error) + if (!this._isReconnecting) { + this.emit('error', err) + } + throw err + } + + onDisconnectError(error) { + const err = new ConnectionError(error) + // no check for reconnecting + this.emit('error', err) + throw err + } + async connect() { this.shouldConnect = true if (this.initialConnectTask) { @@ -275,16 +292,11 @@ export default class Connection extends EventEmitter { // eslint-disable-next-line promise/no-nesting return this.reconnect().catch((error) => { - this.debug('failed reconnect during initial connection', error) + this.debug('failed reconnect during initial connection') throw error }) - }).catch((error) => { - const err = new ConnectionError(error) - if (!this._isReconnecting) { - this.emit('error', err) - } - throw err }) + .catch(this.onConnectError) .finally(() => { if (this.initialConnectTask === initialConnectTask) { this.initialConnectTask = undefined @@ -325,9 +337,9 @@ export default class Connection extends EventEmitter { debug('unexpected close') // eslint-disable-next-line promise/no-nesting - await this.reconnect().catch((error) => { - this.debug('failed reconnect after connected', error) - this.emit('error', error) + await this.reconnect().catch((err) => { + this.debug('failed reconnect after connected') + this.emit('error', new ConnectionError(err)) }) }) return value @@ -380,11 +392,7 @@ export default class Connection extends EventEmitter { } this.socket = undefined - try { - this.emit('disconnected') - } catch (err) { - this.emit('error', err) - } + this.emit('disconnected') if (this.debug === debug) { this.debug = this._debug @@ -392,12 +400,8 @@ export default class Connection extends EventEmitter { }) socket.addEventListener('error', (err) => { - debug('error', err) if (this.socket !== socket) { return } - const error = new ConnectionError(err.error || err) - if (!this._isReconnecting) { - this.emit('error', error) - } + this.emit('error', new ConnectionError(err)) }) this.emitTransition('connected') @@ -408,6 +412,10 @@ export default class Connection extends EventEmitter { return Promise.resolve() } + if (this.disconnectTask) { + return this.disconnectTask + } + return new Promise((resolve, reject) => { let onError const onDisconnected = () => { @@ -430,10 +438,8 @@ export default class Connection extends EventEmitter { return this.disconnectTask } const disconnectTask = this._disconnect() - .catch((err) => { - this.emit('error', err) - throw err - }).finally(() => { + .catch(this.onDisconnectError) + .finally(() => { if (this.disconnectTask === disconnectTask) { this.disconnectTask = undefined } @@ -476,6 +482,10 @@ export default class Connection extends EventEmitter { return Promise.resolve() } + if (this.initialConnectTask) { + return this.initialConnectTask + } + return new Promise((resolve, reject) => { let onError const onConnected = () => { diff --git a/test/unit/Connection.test.js b/test/unit/Connection.test.js index 7ecca9271..159743794 100644 --- a/test/unit/Connection.test.js +++ b/test/unit/Connection.test.js @@ -466,7 +466,7 @@ describe('Connection', () => { expectErrors = 1 await s.connect() s.options.url = 'badurl' - s.on('error', async (err) => { + s.once('error', async (err) => { expect(err).toBeTruthy() expect(onConnected).toHaveBeenCalledTimes(1) expect(s.isDisconnected()).toBeTruthy() From 5319129880350063282139e66b596bdcd5c1355c Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 15 Sep 2020 13:49:15 -0400 Subject: [PATCH 045/517] Remove client.handleError, make emit/throw & ignoring of Connection errors consistent. --- src/Publisher.js | 15 ++++----------- src/Resender.js | 10 +++------- src/StreamrClient.js | 17 ++++++++++++----- src/Subscriber.js | 20 +++++++++++--------- test/integration/StreamrClient.test.js | 3 --- 5 files changed, 30 insertions(+), 35 deletions(-) diff --git a/src/Publisher.js b/src/Publisher.js index cc0f6151a..7f37ff3b8 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -30,28 +30,21 @@ export default class Publisher { debug: client.debug, }, client.options.publishWithSignature) + this.onError = this.client.getErrorHandler(this) + if (client.session.isUnauthenticated()) { this.msgCreationUtil = null } else { this.msgCreationUtil = new MessageCreationUtil( client.options.auth, this.signer, once(() => client.getUserInfo()), - (streamId) => client.getStream(streamId) - .catch((err) => client.emit('error', err)) + (streamId) => client.getStream(streamId).catch(this.onError) ) } } async publish(...args) { this.debug('publish()') - return this._publish(...args).catch((err) => { - if (!(err instanceof Connection.ConnectionError || err.reason instanceof Connection.ConnectionError)) { - // emit non-connection errors - this.client.emit('error', err) - } else { - this.debug(err) - } - throw err - }) + return this._publish(...args).catch(this.onError) } async _publish(streamObjectOrId, data, timestamp = new Date(), partitionKey = null) { diff --git a/src/Resender.js b/src/Resender.js index c884b5af8..ef01bd09e 100644 --- a/src/Resender.js +++ b/src/Resender.js @@ -13,13 +13,13 @@ export default class Resender { constructor(client) { this.client = client this.debug = client.debug.extend('Resends') + this.onError = this.client.getErrorHandler(this) this.resendUtil = new ResendUtil({ debug: this.debug, }) - this.onResendUtilError = this.onResendUtilError.bind(this) - this.resendUtil.on('error', this.onResendUtilError) + this.resendUtil.on('error', this.onError) // Unicast messages to a specific subscription only this.onUnicastMessage = this.onUnicastMessage.bind(this) @@ -36,10 +36,6 @@ export default class Resender { this.client.connection.on(ControlMessage.TYPES.ResendResponseResent, this.onResendResponseResent) } - onResendUtilError(err) { - this.client.emit('error', err) - } - onResendResponseResent(response) { // eslint-disable-next-line no-underscore-dangle const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) @@ -133,7 +129,7 @@ export default class Resender { sub.setState(Subscription.State.subscribed) // eslint-disable-next-line no-underscore-dangle sub.once('initial_resend_done', () => this.client.subscriber._removeSubscription(sub)) - await this._requestResend(sub) + await this._requestResend(sub).catch(this.onError) return sub } diff --git a/src/StreamrClient.js b/src/StreamrClient.js index d8962741f..4b980fa2f 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -135,6 +135,18 @@ export default class StreamrClient extends EventEmitter { } } + getErrorHandler(source) { + return (err) => { + if (!(err instanceof Connection.ConnectionError || err.reason instanceof Connection.ConnectionError)) { + // emit non-connection errors + this.emit('error', err) + } else { + source.debug(err) + } + throw err + } + } + onErrorResponse(err) { const errorObject = new Error(err.errorMessage) this.emit('error', errorObject) @@ -160,11 +172,6 @@ export default class StreamrClient extends EventEmitter { console.error(error) } - handleError(msg) { - this.debug(msg) - this.emit('error', msg) - } - async resend(...args) { return this.resender.resend(...args) } diff --git a/src/Subscriber.js b/src/Subscriber.js index 473f64208..abbd89404 100644 --- a/src/Subscriber.js +++ b/src/Subscriber.js @@ -29,6 +29,8 @@ export default class Subscriber { this.onClientDisconnected = this.onClientDisconnected.bind(this) this.client.on('disconnected', this.onClientDisconnected) + + this.onError = this.client.getErrorHandler(this) } onBroadcastMessage(msg) { @@ -76,14 +78,12 @@ export default class Subscriber { Object.keys(this.subscribedStreamPartitions).forEach((key) => { this.subscribedStreamPartitions[key].getSubscriptions().forEach((sub) => { if (sub.getState() !== Subscription.State.subscribed) { - this._resendAndSubscribe(sub).catch((err) => { - this.client.emit('error', err) - }) + this._resendAndSubscribe(sub).catch(this.onError) } }) }) } catch (err) { - this.client.emit('error', err) + this.onError(err) } } @@ -163,12 +163,14 @@ export default class Subscriber { // eslint-disable-next-line no-underscore-dangle this.client.resender._requestResend(sub, { from, to, publisherId, msgChainId, - }) + }).catch((err) => ( + this.onError(new Error(`Failed to send resend request: ${err.stack}`)) + )) } }) sub.on('done', () => { this.debug('done event for sub %s', sub.id) - this.unsubscribe(sub) + this.unsubscribe(sub).catch(this.onError) }) // Add to lookups @@ -176,7 +178,7 @@ export default class Subscriber { // If connected, emit a subscribe request if (this.client.isConnected()) { - this._resendAndSubscribe(sub) + this._resendAndSubscribe(sub).catch(this.onError) } else if (this.client.options.autoConnect) { this.client.ensureConnected() } @@ -346,7 +348,7 @@ export default class Subscriber { this.debug('_requestSubscribe: subscribing client: %o', request) await this.client.send(request).catch((err) => { sub.setState(Subscription.State.unsubscribed) - this.client.emit('error', new Error(`Failed to send subscribe request: ${err.stack}`)) + return this.onError(new Error(`Failed to sendnsubscribe request: ${err.stack}`)) }) } @@ -359,7 +361,7 @@ export default class Subscriber { }) await this.client.connection.send(unsubscribeRequest).catch((err) => { sub.setState(Subscription.State.subscribed) - this.client.handleError(`Failed to send unsubscribe request: ${err.stack}`) + return this.onError(new Error(`Failed to send unsubscribe request: ${err.stack}`)) }) return this._checkAutoDisconnect() } diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index c6bcf04fe..f074b14c6 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -5,7 +5,6 @@ import fetch from 'node-fetch' import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import { wait, waitForEvent } from 'streamr-test-utils' import { ethers } from 'ethers' -import Debug from 'debug' import { uid } from '../utils' import StreamrClient from '../../src' @@ -13,8 +12,6 @@ import Connection from '../../src/Connection' import config from './config' -const debug = Debug('StreamrClient').extend('test') - const { StreamMessage } = MessageLayer const WebSocket = require('ws') From 9fea7ffb0e513d49b84ae47d3c6d00893d4012aa Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 15 Sep 2020 14:15:38 -0400 Subject: [PATCH 046/517] Don't throw, only emit errors for unchained async tasks. --- src/Publisher.js | 9 ++++++--- src/Resender.js | 9 ++++++--- src/StreamrClient.js | 5 +++-- src/Subscriber.js | 24 ++++++++++++++---------- test/unit/StreamrClient.test.js | 2 +- 5 files changed, 30 insertions(+), 19 deletions(-) diff --git a/src/Publisher.js b/src/Publisher.js index 7f37ff3b8..0cc2e85c2 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -30,21 +30,24 @@ export default class Publisher { debug: client.debug, }, client.options.publishWithSignature) - this.onError = this.client.getErrorHandler(this) + this.onErrorEmit = this.client.getErrorEmitter(this) if (client.session.isUnauthenticated()) { this.msgCreationUtil = null } else { this.msgCreationUtil = new MessageCreationUtil( client.options.auth, this.signer, once(() => client.getUserInfo()), - (streamId) => client.getStream(streamId).catch(this.onError) + (streamId) => client.getStream(streamId).catch(this.onErrorEmit) ) } } async publish(...args) { this.debug('publish()') - return this._publish(...args).catch(this.onError) + return this._publish(...args).catch((err) => { + this.onErrorEmit(err) + throw err + }) } async _publish(streamObjectOrId, data, timestamp = new Date(), partitionKey = null) { diff --git a/src/Resender.js b/src/Resender.js index ef01bd09e..93342b067 100644 --- a/src/Resender.js +++ b/src/Resender.js @@ -13,13 +13,13 @@ export default class Resender { constructor(client) { this.client = client this.debug = client.debug.extend('Resends') - this.onError = this.client.getErrorHandler(this) + this.onErrorEmit = this.client.getErrorEmitter(this) this.resendUtil = new ResendUtil({ debug: this.debug, }) - this.resendUtil.on('error', this.onError) + this.resendUtil.on('error', this.onErrorEmit) // Unicast messages to a specific subscription only this.onUnicastMessage = this.onUnicastMessage.bind(this) @@ -129,7 +129,10 @@ export default class Resender { sub.setState(Subscription.State.subscribed) // eslint-disable-next-line no-underscore-dangle sub.once('initial_resend_done', () => this.client.subscriber._removeSubscription(sub)) - await this._requestResend(sub).catch(this.onError) + await this._requestResend(sub).catch((err) => { + this.onErrorEmit(err) + throw err + }) return sub } diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 4b980fa2f..d047dc7a6 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -81,9 +81,11 @@ export default class StreamrClient extends EventEmitter { this.getUserInfo = this.getUserInfo.bind(this) this.onConnectionConnected = this.onConnectionConnected.bind(this) this.onConnectionDisconnected = this.onConnectionDisconnected.bind(this) + this._onError = this._onError.bind(this) this.onErrorResponse = this.onErrorResponse.bind(this) this.onConnectionError = this.onConnectionError.bind(this) + this.getErrorEmitter = this.getErrorEmitter.bind(this) this.on('error', this._onError) // attach before creating sub-components incase they fire error events @@ -135,7 +137,7 @@ export default class StreamrClient extends EventEmitter { } } - getErrorHandler(source) { + getErrorEmitter(source) { return (err) => { if (!(err instanceof Connection.ConnectionError || err.reason instanceof Connection.ConnectionError)) { // emit non-connection errors @@ -143,7 +145,6 @@ export default class StreamrClient extends EventEmitter { } else { source.debug(err) } - throw err } } diff --git a/src/Subscriber.js b/src/Subscriber.js index abbd89404..27473ef00 100644 --- a/src/Subscriber.js +++ b/src/Subscriber.js @@ -30,7 +30,7 @@ export default class Subscriber { this.onClientDisconnected = this.onClientDisconnected.bind(this) this.client.on('disconnected', this.onClientDisconnected) - this.onError = this.client.getErrorHandler(this) + this.onErrorEmit = this.client.getErrorEmitter(this) } onBroadcastMessage(msg) { @@ -78,12 +78,12 @@ export default class Subscriber { Object.keys(this.subscribedStreamPartitions).forEach((key) => { this.subscribedStreamPartitions[key].getSubscriptions().forEach((sub) => { if (sub.getState() !== Subscription.State.subscribed) { - this._resendAndSubscribe(sub).catch(this.onError) + this._resendAndSubscribe(sub).catch(this.onErrorEmit) } }) }) } catch (err) { - this.onError(err) + this.onErrorEmit(err) } } @@ -163,14 +163,14 @@ export default class Subscriber { // eslint-disable-next-line no-underscore-dangle this.client.resender._requestResend(sub, { from, to, publisherId, msgChainId, - }).catch((err) => ( - this.onError(new Error(`Failed to send resend request: ${err.stack}`)) - )) + }).catch((err) => { + this.onErrorEmit(new Error(`Failed to send resend request: ${err.stack}`)) + }) } }) sub.on('done', () => { this.debug('done event for sub %s', sub.id) - this.unsubscribe(sub).catch(this.onError) + this.unsubscribe(sub).catch(this.onErrorEmit) }) // Add to lookups @@ -178,7 +178,7 @@ export default class Subscriber { // If connected, emit a subscribe request if (this.client.isConnected()) { - this._resendAndSubscribe(sub).catch(this.onError) + this._resendAndSubscribe(sub).catch(this.onErrorEmit) } else if (this.client.options.autoConnect) { this.client.ensureConnected() } @@ -348,7 +348,9 @@ export default class Subscriber { this.debug('_requestSubscribe: subscribing client: %o', request) await this.client.send(request).catch((err) => { sub.setState(Subscription.State.unsubscribed) - return this.onError(new Error(`Failed to sendnsubscribe request: ${err.stack}`)) + const error = new Error(`Failed to sendnsubscribe request: ${err.stack}`) + this.onErrorEmit(error) + throw error }) } @@ -361,7 +363,9 @@ export default class Subscriber { }) await this.client.connection.send(unsubscribeRequest).catch((err) => { sub.setState(Subscription.State.subscribed) - return this.onError(new Error(`Failed to send unsubscribe request: ${err.stack}`)) + const error = new Error(`Failed to send unsubscribe request: ${err.stack}`) + this.onErrorEmit(error) + throw error }) return this._checkAutoDisconnect() } diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index 8dbf65bc1..8d73f834d 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -1167,7 +1167,7 @@ describe('StreamrClient', () => { it('unsubscribes', (done) => { const sub = mockSubscription('stream1', () => {}) - client.subscriber.unsubscribe = (unsub) => { + client.subscriber.unsubscribe = async (unsub) => { expect(sub).toBe(unsub) done() } From 9e7c279e337d980e8ebeddf1e07a3476d798573c Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 15 Sep 2020 14:35:14 -0400 Subject: [PATCH 047/517] Disable logging in long resend test. --- test/integration/Resends.test.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/integration/Resends.test.js b/test/integration/Resends.test.js index 1246adaea..1bba64859 100644 --- a/test/integration/Resends.test.js +++ b/test/integration/Resends.test.js @@ -1,3 +1,5 @@ +import Debug from 'debug' + import { uid } from '../utils' import StreamrClient from '../../src' @@ -299,6 +301,8 @@ describe('StreamrClient resends', () => { }, 40000) it('long resend', async (done) => { + client.debug('disabling verbose logging') + Debug.disable() const LONG_RESEND = 10000 const publishedMessages2 = [] From 5cf95ea54ab73a525b638add8175edfd7471ea18 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 15 Sep 2020 14:43:02 -0400 Subject: [PATCH 048/517] Clean up unused imports. --- src/Publisher.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Publisher.js b/src/Publisher.js index 0cc2e85c2..a13610454 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -5,7 +5,6 @@ import Signer from './Signer' import Stream from './rest/domain/Stream' import FailedToPublishError from './errors/FailedToPublishError' import MessageCreationUtil from './MessageCreationUtil' -import Connection from './Connection' function getStreamId(streamObjectOrId) { if (streamObjectOrId instanceof Stream) { From 9ccab8116d935589f65360485d5955add793c826 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Sat, 12 Sep 2020 11:36:51 -0400 Subject: [PATCH 049/517] Promisify subscribe/unsubscribe. --- src/Subscriber.js | 42 +- src/Subscription.js | 39 ++ test/integration/MultipleClients.test.js | 20 +- test/integration/ResendReconnect.test.js | 4 +- test/integration/Resends.test.js | 35 +- test/integration/StreamrClient.test.js | 152 ++---- test/integration/Subscription.test.js | 122 ++--- test/unit/StreamrClient.test.js | 656 +++++++++++------------ 8 files changed, 521 insertions(+), 549 deletions(-) diff --git a/src/Subscriber.js b/src/Subscriber.js index 27473ef00..21ab24325 100644 --- a/src/Subscriber.js +++ b/src/Subscriber.js @@ -111,7 +111,16 @@ export default class Subscriber { } } - subscribe(optionsOrStreamId, callback, legacyOptions) { + async subscribe(...args) { + const sub = this.createSubscription(...args) + await Promise.all([ + sub.waitForSubscribed(), + this._resendAndSubscribe(sub), + ]) + return sub + } + + createSubscription(optionsOrStreamId, callback, legacyOptions) { const options = this._validateParameters(optionsOrStreamId, callback) // Backwards compatibility for giving an options object as third argument @@ -176,13 +185,6 @@ export default class Subscriber { // Add to lookups this._addSubscription(sub) - // If connected, emit a subscribe request - if (this.client.isConnected()) { - this._resendAndSubscribe(sub).catch(this.onErrorEmit) - } else if (this.client.options.autoConnect) { - this.client.ensureConnected() - } - return sub } @@ -191,6 +193,21 @@ export default class Subscriber { throw new Error('unsubscribe: please give a Subscription object as an argument!') } + if (sub.getState() === Subscription.State.unsubscribed) { + return Promise.resolve() + } + + return Promise.all([ + new Promise((resolve) => sub.once('unsubscribed', resolve)), + this._sendUnsubscribe(sub) + ]).then(() => Promise.resolve()) + } + + async _sendUnsubscribe(sub) { + if (!sub || !sub.streamId) { + throw new Error('unsubscribe: please give a Subscription object as an argument!') + } + const { streamId, streamPartition, id } = sub const info = { id, @@ -323,16 +340,16 @@ export default class Subscriber { if (subscribedSubs.length) { // If there already is a subscribed subscription for this stream, this new one will just join it immediately this.debug('_requestSubscribe: another subscription for same stream: %s, insta-subscribing', sub.streamId) - - setTimeout(() => { - sub.setState(Subscription.State.subscribed) - }) + await true // wait a tick + sub.setState(Subscription.State.subscribed) return } } const sessionToken = await this.client.session.getSessionToken() + // this should come after an async call e.g. getSessionToken + // so only one parallel call will send the subscription request if (sp.isSubscribing()) { return } @@ -344,6 +361,7 @@ export default class Subscriber { sessionToken, requestId: this.client.resender.resendUtil.generateRequestId(), }) + sp.setSubscribing(true) this.debug('_requestSubscribe: subscribing client: %o', request) await this.client.send(request).catch((err) => { diff --git a/src/Subscription.js b/src/Subscription.js index 0f2d9ec98..126b67029 100644 --- a/src/Subscription.js +++ b/src/Subscription.js @@ -41,6 +41,45 @@ export default class Subscription extends EventEmitter { this.state = Subscription.State.unsubscribed } + async waitForSubscribed() { + if (this._subscribedPromise) { + return this._subscribedPromise + } + + const subscribedPromise = new Promise((resolve, reject) => { + if (this.state === Subscription.State.subscribed) { + resolve() + return + } + let onError + const onSubscribed = () => { + this.off('error', onError) + resolve() + } + onError = (err) => { + this.off('subscribed', onSubscribed) + reject(err) + } + + const onUnsubscribed = () => { + if (this._subscribedPromise === subscribedPromise) { + this._subscribedPromise = undefined + } + } + + this.once('subscribed', onSubscribed) + this.once('unsubscribed', onUnsubscribed) + this.once('error', reject) + }).then(() => this).finally(() => { + if (this._subscribedPromise === subscribedPromise) { + this._subscribedPromise = undefined + } + }) + + this._subscribedPromise = subscribedPromise + return this._subscribedPromise + } + emit(event, ...args) { this.debug('emit', event) return super.emit(event, ...args) diff --git a/test/integration/MultipleClients.test.js b/test/integration/MultipleClients.test.js index 92b30d8ec..8dbf29912 100644 --- a/test/integration/MultipleClients.test.js +++ b/test/integration/MultipleClients.test.js @@ -75,20 +75,16 @@ describe('PubSub with multiple clients', () => { const receivedMessagesOther = [] const receivedMessagesMain = [] // subscribe to stream from other client instance - await new Promise((resolve) => { - otherClient.subscribe({ - stream: stream.id, - }, (msg) => { - receivedMessagesOther.push(msg) - }).once('subscribed', resolve) + await otherClient.subscribe({ + stream: stream.id, + }, (msg) => { + receivedMessagesOther.push(msg) }) // subscribe to stream from main client instance - await new Promise((resolve) => { - mainClient.subscribe({ - stream: stream.id, - }, (msg) => { - receivedMessagesMain.push(msg) - }).once('subscribed', resolve) + await mainClient.subscribe({ + stream: stream.id, + }, (msg) => { + receivedMessagesMain.push(msg) }) const message = { msg: uid('message'), diff --git a/test/integration/ResendReconnect.test.js b/test/integration/ResendReconnect.test.js index 0d79942a5..c324dac11 100644 --- a/test/integration/ResendReconnect.test.js +++ b/test/integration/ResendReconnect.test.js @@ -57,8 +57,8 @@ describe('resend/reconnect', () => { describe('reconnect after resend', () => { let sub let messages = [] - beforeEach((done) => { - sub = client.subscribe({ + beforeEach(async (done) => { + sub = await client.subscribe({ stream: stream.id, resend: { last: MAX_MESSAGES, diff --git a/test/integration/Resends.test.js b/test/integration/Resends.test.js index 1bba64859..7e84cf595 100644 --- a/test/integration/Resends.test.js +++ b/test/integration/Resends.test.js @@ -26,7 +26,7 @@ describe('StreamrClient resends', () => { beforeEach(async () => { client = createClient() - await client.ensureConnected() + await client.connect() publishedMessages = [] @@ -69,7 +69,7 @@ describe('StreamrClient resends', () => { resentMessages.push(message) }) - client.subscribe({ + await client.subscribe({ stream: stream.id, }, (message) => { realtimeMessages.push(message) @@ -92,7 +92,7 @@ describe('StreamrClient resends', () => { msg: uid('realtimeMessage'), } - client.subscribe({ + await client.subscribe({ stream: stream.id, }, (message) => { realtimeMessages.push(message) @@ -217,32 +217,25 @@ describe('StreamrClient resends', () => { const receivedMessages = [] // eslint-disable-next-line no-await-in-loop - const sub = client.subscribe( - { - stream: stream.id, - resend: { - last: MAX_MESSAGES, - }, - }, - (message) => { - receivedMessages.push(message) + const sub = await client.subscribe({ + stream: stream.id, + resend: { + last: MAX_MESSAGES, }, - ) - - // eslint-disable-next-line no-loop-func - sub.once('resent', () => { - expect(receivedMessages).toStrictEqual(publishedMessages) + }, (message) => { + receivedMessages.push(message) }) - // eslint-disable-next-line no-await-in-loop - await waitForCondition(() => receivedMessages.length === MAX_MESSAGES, 10000) + // eslint-disable-next-line no-loop-func + await waitForEvent(sub, 'resent') + expect(receivedMessages).toStrictEqual(publishedMessages) }, 10000) } it('resend last using subscribe and publish messages after resend', async () => { const receivedMessages = [] - client.subscribe({ + await client.subscribe({ stream: stream.id, resend: { last: MAX_MESSAGES, @@ -273,7 +266,7 @@ describe('StreamrClient resends', () => { it('resend last using subscribe and publish realtime messages', async () => { const receivedMessages = [] - const sub = client.subscribe({ + const sub = await client.subscribe({ stream: stream.id, resend: { last: MAX_MESSAGES, diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index f074b14c6..55d68e9e6 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -505,7 +505,7 @@ describe('StreamrClient', () => { }).rejects.toThrow() }, 5000) - it('should not subscribe to unsubscribed streams on reconnect', async (done) => { + it('should not subscribe to unsubscribed streams on reconnect', async () => { client = createClient() await client.connect() const sessionToken = await client.session.getSessionToken() @@ -515,37 +515,29 @@ describe('StreamrClient', () => { }) const connectionEventSpy = jest.spyOn(client.connection, '_send') - const sub = client.subscribe(stream.id, () => {}) - - sub.once('subscribed', async () => { - await wait(100) - await client.unsubscribe(sub) - }) - - sub.once('unsubscribed', async () => { - await client.disconnect() - await client.connect() - await client.disconnect() - // key exchange stream subscription should not have been sent yet - expect(connectionEventSpy.mock.calls).toHaveLength(2) - - // check whole list of calls after reconnect and disconnect - expect(connectionEventSpy.mock.calls[0]).toEqual([new SubscribeRequest({ - streamId: stream.id, - streamPartition: 0, - sessionToken, - requestId: connectionEventSpy.mock.calls[0][0].requestId, - })]) - - expect(connectionEventSpy.mock.calls[1]).toEqual([new UnsubscribeRequest({ - streamId: stream.id, - streamPartition: 0, - sessionToken, - requestId: connectionEventSpy.mock.calls[1][0].requestId, - })]) - - done() - }) + const sub = await client.subscribe(stream.id, () => {}) + await wait(100) + await client.unsubscribe(sub) + await client.disconnect() + await client.connect() + await client.disconnect() + // key exchange stream subscription should not have been sent yet + expect(connectionEventSpy.mock.calls).toHaveLength(2) + + // check whole list of calls after reconnect and disconnect + expect(connectionEventSpy.mock.calls[0]).toEqual([new SubscribeRequest({ + streamId: stream.id, + streamPartition: 0, + sessionToken, + requestId: connectionEventSpy.mock.calls[0][0].requestId, + })]) + + expect(connectionEventSpy.mock.calls[1]).toEqual([new UnsubscribeRequest({ + streamId: stream.id, + streamPartition: 0, + sessionToken, + requestId: connectionEventSpy.mock.calls[1][0].requestId, + })]) }) it('should not subscribe after resend() on reconnect', async (done) => { @@ -699,18 +691,16 @@ describe('StreamrClient', () => { name: uid('stream') }) - const sub = client.subscribe({ + await client.subscribe({ stream: stream.id, }, () => {}) - sub.once('subscribed', async () => { - await client.disconnect() - // wait in case of delayed errors - setTimeout(() => { - expect(client.onError).not.toHaveBeenCalled() - done() - }, 100) - }) + await client.disconnect() + // wait in case of delayed errors + setTimeout(() => { + expect(client.onError).not.toHaveBeenCalled() + done() + }, 100) }) it('does not error if disconnect after subscribe with resend', async (done) => { @@ -724,7 +714,7 @@ describe('StreamrClient', () => { name: uid('stream') }) - const sub = client.subscribe({ + await client.subscribe({ stream: stream.id, resend: { from: { @@ -733,14 +723,12 @@ describe('StreamrClient', () => { }, }, () => {}) - sub.once('subscribed', async () => { - await client.disconnect() - // wait in case of delayed errors - setTimeout(() => { - expect(client.onError).not.toHaveBeenCalled() - done() - }, 100) - }) + await client.disconnect() + // wait in case of delayed errors + setTimeout(() => { + expect(client.onError).not.toHaveBeenCalled() + done() + }, 100) }) }) }) @@ -823,27 +811,21 @@ describe('StreamrClient', () => { it('client.subscribe then unsubscribe after subscribed without resend', async () => { expect(client.getSubscriptions(stream.id)).toHaveLength(0) - const sub = client.subscribe({ + const sub = await client.subscribe({ stream: stream.id, }, () => {}) - const onSubscribed = jest.fn() - sub.on('subscribed', onSubscribed) const onUnsubscribed = jest.fn() sub.on('unsubscribed', onUnsubscribed) expect(client.getSubscriptions(stream.id)).toHaveLength(1) // has subscription immediately - await new Promise((resolve) => sub.once('subscribed', resolve)) expect(client.getSubscriptions(stream.id)).toHaveLength(1) - const t = new Promise((resolve) => sub.once('unsubscribed', resolve)) await client.unsubscribe(sub) - await t expect(client.getSubscriptions(stream.id)).toHaveLength(0) - expect(onSubscribed).toHaveBeenCalledTimes(1) expect(onUnsubscribed).toHaveBeenCalledTimes(1) }, TIMEOUT) - it('client.subscribe then unsubscribe before subscribed without resend', async () => { + it.skip('client.subscribe then unsubscribe before subscribed without resend', async () => { expect(client.getSubscriptions(stream.id)).toHaveLength(0) const sub = client.subscribe({ @@ -863,7 +845,7 @@ describe('StreamrClient', () => { expect(onUnsubscribed).toHaveBeenCalledTimes(1) }, TIMEOUT) - it('client.subscribe then unsubscribe before subscribed with resend', async () => { + it.skip('client.subscribe then unsubscribe before subscribed with resend', async () => { expect(client.getSubscriptions(stream.id)).toHaveLength(0) const sub = client.subscribe({ @@ -888,7 +870,7 @@ describe('StreamrClient', () => { expect(onUnsubscribed).toHaveBeenCalledTimes(1) }, TIMEOUT) - it('client.subscribe then unsubscribe before subscribed with resend', async () => { + it.skip('client.subscribe then unsubscribe before subscribed with resend', async () => { expect(client.getSubscriptions(stream.id)).toHaveLength(0) const sub = client.subscribe({ @@ -923,7 +905,7 @@ describe('StreamrClient', () => { expect(client.getSubscriptions(stream.id)).toHaveLength(0) const onMessage = jest.fn() - const sub = client.subscribe({ + const sub = await client.subscribe({ stream: stream.id, }, onMessage) @@ -936,7 +918,6 @@ describe('StreamrClient', () => { sub.on('no_resend', onNoResend) const onUnsubscribed = jest.fn() sub.on('unsubscribed', onUnsubscribed) - await new Promise((resolve) => sub.once('subscribed', resolve)) const msg = { name: uid('msg') } @@ -948,7 +929,7 @@ describe('StreamrClient', () => { expect(onResent).toHaveBeenCalledTimes(0) expect(onMessage).toHaveBeenCalledTimes(0) expect(onNoResend).toHaveBeenCalledTimes(0) - expect(onSubscribed).toHaveBeenCalledTimes(1) + expect(onSubscribed).toHaveBeenCalledTimes(0) expect(onUnsubscribed).toHaveBeenCalledTimes(1) }, TIMEOUT) @@ -960,7 +941,7 @@ describe('StreamrClient', () => { await wait(TIMEOUT * 0.5) const onMessage = jest.fn() - const sub = client.subscribe({ + const sub = await client.subscribe({ stream: stream.id, resend: { from: { @@ -978,26 +959,19 @@ describe('StreamrClient', () => { sub.on('no_resend', onNoResend) const onUnsubscribed = jest.fn() sub.on('unsubscribed', onUnsubscribed) - client.debug(1) - await new Promise((resolve) => sub.once('subscribed', resolve)) - client.debug(2) - const t = new Promise((resolve) => sub.once('unsubscribed', resolve)) await client.unsubscribe(sub) - client.debug(3) - await t - client.debug(4) expect(client.getSubscriptions(stream.id)).toHaveLength(0) // lost subscription immediately expect(onResent).toHaveBeenCalledTimes(0) expect(onMessage).toHaveBeenCalledTimes(0) expect(onNoResend).toHaveBeenCalledTimes(0) - expect(onSubscribed).toHaveBeenCalledTimes(1) + expect(onSubscribed).toHaveBeenCalledTimes(0) expect(onUnsubscribed).toHaveBeenCalledTimes(1) }, TIMEOUT * 2) }) it('client.subscribe (realtime)', async (done) => { const id = Date.now() - const sub = client.subscribe({ + const sub = await client.subscribe({ stream: stream.id, }, async (parsedContent, streamMessage) => { expect(parsedContent.id).toBe(id) @@ -1013,10 +987,8 @@ describe('StreamrClient', () => { }) // Publish after subscribed - sub.once('subscribed', () => { - stream.publish({ - id, - }) + await stream.publish({ + id, }) }) @@ -1025,7 +997,7 @@ describe('StreamrClient', () => { const nbMessages = 3 const intervalMs = 100 let counter = 0 - const sub = client.subscribe({ + const sub = await client.subscribe({ stream: stream.id, }, async (parsedContent, streamMessage) => { expect(parsedContent.i).toBe(counter) @@ -1051,7 +1023,6 @@ describe('StreamrClient', () => { }) // Publish after subscribed - await new Promise((resolve) => sub.once('subscribed', resolve)) for (let i = 0; i < nbMessages; i++) { // eslint-disable-next-line no-await-in-loop await wait(intervalMs) @@ -1073,7 +1044,7 @@ describe('StreamrClient', () => { // Add delay: this test needs some time to allow the message to be written to Cassandra await wait(TIMEOUT * 0.8) - const sub = client.subscribe({ + const sub = await client.subscribe({ stream: stream.id, resend: { from: { @@ -1096,9 +1067,7 @@ describe('StreamrClient', () => { expect(streamMessage.signature).toBeTruthy() // All good, unsubscribe - const t = new Promise((resolve) => sub.once('unsubscribed', resolve)) await client.unsubscribe(sub) - await t expect(client.getSubscriptions(stream.id)).toHaveLength(0) done() }) @@ -1116,7 +1085,7 @@ describe('StreamrClient', () => { // Add delay: this test needs some time to allow the message to be written to Cassandra await wait(TIMEOUT * 0.7) - const sub = client.subscribe({ + const sub = await client.subscribe({ stream: stream.id, resend: { last: 1, @@ -1137,17 +1106,15 @@ describe('StreamrClient', () => { expect(streamMessage.signature).toBeTruthy() // All good, unsubscribe - const t = new Promise((resolve) => sub.once('unsubscribed', resolve)) await client.unsubscribe(sub) - await t expect(client.getSubscriptions(stream.id)).toHaveLength(0) done() }) }, TIMEOUT) - it('client.subscribe (realtime with resend)', (done) => { + it('client.subscribe (realtime with resend)', async (done) => { const id = Date.now() - const sub = client.subscribe({ + const sub = await client.subscribe({ stream: stream.id, resend: { last: 1, @@ -1166,10 +1133,8 @@ describe('StreamrClient', () => { }) // Publish after subscribed - sub.once('subscribed', () => { - stream.publish({ - id, - }) + await stream.publish({ + id, }) }, 30000) }) @@ -1181,12 +1146,11 @@ describe('StreamrClient', () => { it('decodes realtime messages correctly', async (done) => { client.once('error', done) - client.subscribe(stream.id, (msg) => { + await client.subscribe(stream.id, (msg) => { expect(msg).toStrictEqual(publishedMessage) done() - }).once('subscribed', () => { - client.publish(stream.id, publishedMessage) }) + await client.publish(stream.id, publishedMessage) }) it('decodes resent messages correctly', async (done) => { diff --git a/test/integration/Subscription.test.js b/test/integration/Subscription.test.js index 7482dff1e..17c7d6a5b 100644 --- a/test/integration/Subscription.test.js +++ b/test/integration/Subscription.test.js @@ -1,5 +1,5 @@ import { ethers } from 'ethers' -import { wait } from 'streamr-test-utils' +import { wait, waitForEvent } from 'streamr-test-utils' import { uid } from '../utils' import StreamrClient from '../../src' @@ -16,8 +16,6 @@ const createClient = (opts = {}) => new StreamrClient({ ...opts, }) -const throwError = (error) => { throw error } - const RESEND_ALL = { from: { timestamp: 0, @@ -28,31 +26,11 @@ describe('Subscription', () => { let stream let client let subscription + let errors = [] + let expectedErrors = 0 - async function setup() { - client = createClient() - client.on('error', throwError) - stream = await client.createStream({ - name: uid('stream') - }) - } - - async function teardown() { - if (subscription) { - await client.unsubscribe(subscription) - subscription = undefined - } - - if (stream) { - await stream.delete() - stream = undefined - } - - if (client) { - client.off('error', throwError) - await client.disconnect() - client = undefined - } + function onError(err) { + errors.push(err) } /** @@ -60,10 +38,10 @@ describe('Subscription', () => { * Needs to create subscription at same time in order to track message events. */ - function createMonitoredSubscription(opts = {}) { + async function createMonitoredSubscription(opts = {}) { if (!client) { throw new Error('No client') } const events = [] - subscription = client.subscribe({ + subscription = await client.subscribe({ stream: stream.id, resend: RESEND_ALL, ...opts, @@ -88,69 +66,63 @@ describe('Subscription', () => { } beforeEach(async () => { - await teardown() - await setup() + errors = [] + expectedErrors = 0 + client = createClient() + client.on('error', onError) + stream = await client.createStream({ + name: uid('stream') + }) + await client.connect() }) afterEach(async () => { - await teardown() + expect(errors).toHaveLength(expectedErrors) }) - describe('subscribe/unsubscribe events', () => { - it('fires events in correct order', async (done) => { - const subscriptionEvents = createMonitoredSubscription() - subscription.on('subscribed', async () => { - subscription.on('unsubscribed', () => { - expect(subscriptionEvents).toEqual([ - 'subscribed', - 'unsubscribed', - ]) - done() - }) - await client.unsubscribe(subscription) - }) + afterEach(async () => { + if (!client) { return } + client.off('error', onError) + client.debug('disconnecting after test') + await client.disconnect() + }) - await client.connect() + describe('subscribe/unsubscribe events', () => { + it('fires events in correct order 1', async () => { + const subscriptionEvents = await createMonitoredSubscription() + await waitForEvent(subscription, 'no_resend') + await client.unsubscribe(subscription) + expect(subscriptionEvents).toEqual([ + 'no_resend', + 'unsubscribed', + ]) }) }) describe('resending/no_resend events', () => { - it('fires events in correct order', async (done) => { - const subscriptionEvents = createMonitoredSubscription() - subscription.on('no_resend', async () => { - await wait(0) - expect(subscriptionEvents).toEqual([ - 'subscribed', - 'no_resend', - ]) - done() - }) - - await client.connect() + it('fires events in correct order 2', async () => { + const subscriptionEvents = await createMonitoredSubscription() + await waitForEvent(subscription, 'no_resend') + expect(subscriptionEvents).toEqual([ + 'no_resend', + ]) }) }) describe('resending/resent events', () => { - it('fires events in correct order', async (done) => { - await client.connect() + it('fires events in correct order 3', async () => { const message1 = await publishMessage() const message2 = await publishMessage() await wait(5000) // wait for messages to (probably) land in storage - const subscriptionEvents = createMonitoredSubscription() - subscription.on('resent', async () => { - await wait(500) // wait in case messages appear after resent event - expect(subscriptionEvents).toEqual([ - 'subscribed', - 'resending', - message1, - message2, - 'resent', - ]) - done() - }) - subscription.on('no_resend', () => { - done('error: got no_resend, expected: resent') - }) + const subscriptionEvents = await createMonitoredSubscription() + await waitForEvent(subscription, 'resent') + await wait(500) // wait in case messages appear after resent event + expect(subscriptionEvents).toEqual([ + 'resending', + message1, + message2, + 'resent', + ]) }, 20 * 1000) }) }) diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index 8d73f834d..75bfd352a 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -1,7 +1,7 @@ import sinon from 'sinon' import { Wallet } from 'ethers' import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' -import { wait } from 'streamr-test-utils' +import { wait, waitForEvent } from 'streamr-test-utils' import FailedToPublishError from '../../src/errors/FailedToPublishError' import Subscription from '../../src/Subscription' @@ -106,14 +106,13 @@ describe('StreamrClient', () => { errors.push(error) } - function mockSubscription(...opts) { - let sub + async function mockSubscription(...opts) { connection._send = jest.fn(async (request) => { requests.push(request) await wait() if (request.type === ControlMessage.TYPES.SubscribeRequest) { connection.emitMessage(new SubscribeResponse({ - streamId: sub.streamId, + streamId: request.streamId, requestId: request.requestId, streamPartition: request.streamPartition, })) @@ -121,14 +120,13 @@ describe('StreamrClient', () => { if (request.type === ControlMessage.TYPES.UnsubscribeRequest) { connection.emitMessage(new UnsubscribeResponse({ - streamId: sub.streamId, + streamId: request.streamId, requestId: request.requestId, streamPartition: request.streamPartition, })) } }) - sub = client.subscribe(...opts).on('error', onError) - return sub + return client.subscribe(...opts) } const STORAGE_DELAY = 2000 @@ -171,23 +169,24 @@ describe('StreamrClient', () => { }) describe('connecting behaviour', () => { - it('connected event should emit an event on client', (done) => { + it('connected event should emit an event on client', async (done) => { client.once('connected', () => { done() }) - client.connect() + await client.connect() }) it('should not send anything if not subscribed to anything', async () => { - await client.ensureConnected() + await client.connect() expect(connection._send).not.toHaveBeenCalled() }) it('should send pending subscribes', async () => { - client.subscribe('stream1', () => {}).on('error', onError) + const t = mockSubscription('stream1', () => {}) - await client.ensureConnected() + await client.connect() await wait() + await t expect(connection._send.mock.calls).toHaveLength(1) expect(connection._send.mock.calls[0][0]).toMatchObject({ streamId: 'stream1', @@ -196,12 +195,39 @@ describe('StreamrClient', () => { }) }) - it.skip('should send pending subscribes when disconnected and then reconnected', async () => { - client.subscribe('stream1', () => {}).on('error', onError) - await client.ensureConnected() + it('should reconnect subscriptions when connection disconnected before subscribed & reconnected', async () => { + await client.connect() + let subscribed = false + const t = mockSubscription('stream1', () => {}).then((v) => { + subscribed = true + return v + }) connection.socket.close() - await client.ensureConnected() - await wait(100) + expect(subscribed).toBe(false) // shouldn't have subscribed yet + // no connect necessary should connect and subscribe + await t + expect(connection._send.mock.calls).toHaveLength(2) + // On connect + expect(connection._send.mock.calls[0][0]).toMatchObject({ + streamId: 'stream1', + streamPartition, + sessionToken, + }) + + // On reconnect + expect(connection._send.mock.calls[1][0]).toMatchObject({ + streamId: 'stream1', + streamPartition, + sessionToken, + }) + }) + + it('should re-subscribe when subscribed then reconnected', async () => { + await client.connect() + await mockSubscription('stream1', () => {}) + connection.socket.close() + await client.nextConnection() + // no connect necessary should auto-reconnect and subscribe expect(connection._send.mock.calls).toHaveLength(2) // On connect expect(connection._send.mock.calls[0][0]).toMatchObject({ @@ -220,138 +246,123 @@ describe('StreamrClient', () => { // TODO convert and move all super mocked tests to integration }) + describe('promise subscribe behaviour', () => { + beforeEach(async () => client.connect()) + + it('works', async () => { + const sub = await mockSubscription('stream1', () => {}) + expect(sub).toBeTruthy() + expect(sub.streamId).toBe('stream1') + await client.unsubscribe(sub) + expect(client.getSubscriptions(sub.streamId)).toEqual([]) + }) + }) + describe('disconnection behaviour', () => { beforeEach(async () => client.connect()) it('emits disconnected event on client', async (done) => { - client.once('disconnected', done) + client.once('disconnected', () => done()) await connection.disconnect() }) it('removes subscriptions', async () => { - const sub = mockSubscription('stream1', () => {}) - await new Promise((resolve) => sub.once('subscribed', resolve)) + const sub = await mockSubscription('stream1', () => {}) await client.disconnect() expect(client.getSubscriptions(sub.streamId)).toEqual([]) }) it('does not remove subscriptions if disconnected accidentally', async () => { - const sub = client.subscribe('stream1', () => {}) + const sub = await mockSubscription('stream1', () => {}) client.connection.socket.close() - await new Promise((resolve) => client.once('disconnected', resolve)) + await waitForEvent(client, 'disconnected') expect(client.getSubscriptions(sub.streamId)).toEqual([sub]) expect(sub.getState()).toEqual(Subscription.State.unsubscribed) await client.connect() expect(client.getSubscriptions(sub.streamId)).toEqual([sub]) + // re-subscribes + expect(sub.getState()).toEqual(Subscription.State.subscribing) }) it('sets subscription state to unsubscribed', async () => { - const sub = mockSubscription('stream1', () => {}) - await new Promise((resolve) => sub.once('subscribed', resolve)) + const sub = await mockSubscription('stream1', () => {}) await connection.disconnect() expect(sub.getState()).toEqual(Subscription.State.unsubscribed) }) }) describe('SubscribeResponse', () => { - beforeEach(async () => client.ensureConnected()) + beforeEach(async () => client.connect()) - it('marks Subscriptions as subscribed', async (done) => { - const sub = mockSubscription('stream1', () => {}) - sub.once('subscribed', () => { - expect(sub.getState()).toEqual(Subscription.State.subscribed) - done() - }) + it('marks Subscriptions as subscribed', async () => { + const sub = await mockSubscription('stream1', () => {}) + expect(sub.getState()).toEqual(Subscription.State.subscribed) }) - it('generates a requestId without resend', (done) => { - const sub = mockSubscription({ + it('generates a requestId without resend', async () => { + await mockSubscription({ stream: 'stream1', }, () => {}) - sub.once('subscribed', () => { - const { requestId } = requests[0] - expect(requestId).toBeTruthy() - done() - }) + const { requestId } = requests[0] + expect(requestId).toBeTruthy() }) - it('emits a resend request if resend options were given. No second resend if a message is received.', (done) => { - const sub = mockSubscription({ + it('emits a resend request if resend options were given. No second resend if a message is received.', async () => { + const sub = await mockSubscription({ stream: 'stream1', resend: { last: 1, }, }, () => {}) - sub.once('subscribed', async () => { - await wait(100) - const { requestId, type } = requests[requests.length - 1] - expect(type).toEqual(ControlMessage.TYPES.ResendLastRequest) - const streamMessage = getStreamMessage(sub.streamId, {}) - connection.emitMessage(new UnicastMessage({ - requestId, - streamMessage, - })) - await wait(STORAGE_DELAY) - sub.stop() - await wait() - expect(connection._send.mock.calls).toHaveLength(2) // sub + resend - expect(connection._send.mock.calls[1][0]).toMatchObject({ - type: ControlMessage.TYPES.ResendLastRequest, - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId, - numberLast: 1, - sessionToken: 'session-token' - }) - done() + await wait(100) + const { requestId, type } = requests[requests.length - 1] + expect(type).toEqual(ControlMessage.TYPES.ResendLastRequest) + const streamMessage = getStreamMessage(sub.streamId, {}) + connection.emitMessage(new UnicastMessage({ + requestId, + streamMessage, + })) + await wait(STORAGE_DELAY) + sub.stop() + await wait() + expect(connection._send.mock.calls).toHaveLength(2) // sub + resend + expect(connection._send.mock.calls[1][0]).toMatchObject({ + type: ControlMessage.TYPES.ResendLastRequest, + streamId: sub.streamId, + streamPartition: sub.streamPartition, + requestId, + numberLast: 1, + sessionToken: 'session-token' }) }, STORAGE_DELAY + 1000) - it('emits multiple resend requests as per multiple subscriptions. No second resends if messages are received.', async (done) => { - const sub1 = mockSubscription({ - stream: 'stream1', - resend: { - last: 2, - }, - }, () => {}) - const sub2 = mockSubscription({ - stream: 'stream1', - resend: { - last: 1, - }, - }, () => {}) - - let requestId1 - let requestId2 - - await Promise.all([ - new Promise((resolve) => { - sub1.once('subscribed', async () => { - await wait(200) - requestId1 = requests[requests.length - 2].requestId - const streamMessage = getStreamMessage(sub1.streamId, {}) - connection.emitMessage(new UnicastMessage({ - requestId: requestId1, - streamMessage, - })) - resolve() - }) - }), - new Promise((resolve) => { - sub2.once('subscribed', async () => { - await wait(200) - requestId2 = requests[requests.length - 1].requestId - const streamMessage = getStreamMessage(sub2.streamId, {}) - connection.emitMessage(new UnicastMessage({ - requestId: requestId2, - streamMessage, - })) - resolve() - }) - }) + it('emits multiple resend requests as per multiple subscriptions. No second resends if messages are received.', async () => { + const [sub1, sub2] = await Promise.all([ + mockSubscription({ + stream: 'stream1', + resend: { + last: 2, + }, + }, () => {}), + mockSubscription({ + stream: 'stream1', + resend: { + last: 1, + }, + }, () => {}) ]) + const requestId1 = requests.find((r) => r.numberLast === 2).requestId + connection.emitMessage(new UnicastMessage({ + requestId: requestId1, + streamMessage: getStreamMessage(sub1.streamId, {}) + })) + + const requestId2 = requests.find((r) => r.numberLast === 1).requestId + connection.emitMessage(new UnicastMessage({ + requestId: requestId2, + streamMessage: getStreamMessage(sub2.streamId, {}) + })) - await wait(STORAGE_DELAY + 400) sub1.stop() sub2.stop() @@ -371,8 +382,10 @@ describe('StreamrClient', () => { sessionToken: 'session-token', }) ] - // eslint-disable-next-line semi-style - ;[connection._send.mock.calls[1][0], connection._send.mock.calls[2][0]].forEach((actual, index) => { + + const calls = connection._send.mock.calls.filter(([o]) => [requestId1, requestId2].includes(o.requestId)) + expect(calls).toHaveLength(2) + calls.forEach(([actual], index) => { const expected = expectedResponses[index] expect(actual).toMatchObject({ requestId: expected.requestId, @@ -382,17 +395,15 @@ describe('StreamrClient', () => { sessionToken: expected.sessionToken, }) }) - done() }, STORAGE_DELAY + 1000) }) describe('UnsubscribeResponse', () => { // Before each test, client is connected, subscribed, and unsubscribe() is called let sub - beforeEach(async (done) => { - await client.ensureConnected() - sub = mockSubscription('stream1', () => {}) - sub.once('subscribed', () => done()) + beforeEach(async () => { + await client.connect() + sub = await mockSubscription('stream1', () => {}) }) it('removes the subscription', async () => { @@ -435,13 +446,13 @@ describe('StreamrClient', () => { beforeEach(async () => { await client.connect() - sub = mockSubscription('stream1', () => {}) + sub = await mockSubscription('stream1', () => {}) }) - it('should call the message handler of each subscription', () => { + it('should call the message handler of each subscription', async () => { sub.handleBroadcastMessage = jest.fn() - const sub2 = setupSubscription('stream1') + const sub2 = await mockSubscription('stream1', () => {}) sub2.handleBroadcastMessage = jest.fn() const requestId = uid('broadcastMessage') const msg1 = new BroadcastMessage({ @@ -451,6 +462,7 @@ describe('StreamrClient', () => { connection.emitMessage(msg1) expect(sub.handleBroadcastMessage).toHaveBeenCalledWith(msg1.streamMessage, expect.any(Function)) + expect(sub2.handleBroadcastMessage).toHaveBeenCalledWith(msg1.streamMessage, expect.any(Function)) }) it('should not crash if messages are received for unknown streams', () => { @@ -462,14 +474,14 @@ describe('StreamrClient', () => { connection.emitMessage(msg1) }) - it('should ensure that the promise returned by the verification function is cached and returned for all handlers', (done) => { + it('should ensure that the promise returned by the verification function is cached and returned for all handlers', async (done) => { let firstResult sub.handleBroadcastMessage = (message, verifyFn) => { firstResult = verifyFn() expect(firstResult).toBeInstanceOf(Promise) expect(verifyFn()).toBe(firstResult) } - const sub2 = mockSubscription('stream1', () => {}) + const sub2 = await mockSubscription('stream1', () => {}) sub2.handleBroadcastMessage = (message, verifyFn) => { firstResult = verifyFn() expect(firstResult).toBeInstanceOf(Promise) @@ -492,17 +504,14 @@ describe('StreamrClient', () => { describe('UnicastMessage', () => { let sub - beforeEach(async (done) => { + beforeEach(async () => { await client.connect() - sub = mockSubscription({ + sub = await mockSubscription({ stream: 'stream1', resend: { last: 5, }, }, () => {}) - .once('subscribed', () => { - done() - }) }) it('should call the message handler of specified Subscription', async () => { @@ -563,15 +572,14 @@ describe('StreamrClient', () => { describe('ResendResponseResending', () => { let sub - beforeEach(async (done) => { + beforeEach(async () => { await client.connect() - sub = mockSubscription({ + sub = await mockSubscription({ stream: 'stream1', resend: { last: 5, }, }, () => {}) - .once('subscribed', () => done()) }) it('emits event on associated subscription', async () => { @@ -609,14 +617,14 @@ describe('StreamrClient', () => { describe('ResendResponseNoResend', () => { let sub - beforeEach(async (done) => { + beforeEach(async () => { await client.connect() - sub = mockSubscription({ + sub = await mockSubscription({ stream: 'stream1', resend: { last: 5, }, - }, () => {}).once('subscribed', () => done()) + }, () => {}) }) it('calls event handler on subscription', () => { @@ -653,14 +661,14 @@ describe('StreamrClient', () => { describe('ResendResponseResent', () => { let sub - beforeEach(async (done) => { + beforeEach(async () => { await client.connect() - sub = mockSubscription({ + sub = await mockSubscription({ stream: 'stream1', resend: { last: 5, }, - }, () => {}).once('subscribed', () => done()) + }, () => {}) }) it('calls event handler on subscription', () => { @@ -695,14 +703,14 @@ describe('StreamrClient', () => { }) describe('ErrorResponse', () => { - beforeEach(async (done) => { + beforeEach(async () => { await client.connect() - mockSubscription({ + await mockSubscription({ stream: 'stream1', resend: { last: 5, } - }, () => {}).once('subscribed', () => done()) + }, () => {}) }) it('emits an error event on client', (done) => { @@ -714,7 +722,7 @@ describe('StreamrClient', () => { errorCode: 'error code' }) - client.once('error', async (err) => { + client.once('error', (err) => { errors.pop() expect(err.message).toEqual(errorResponse.errorMessage) expect(client.onError).toHaveBeenCalled() @@ -727,9 +735,9 @@ describe('StreamrClient', () => { describe('error', () => { let sub - beforeEach(async (done) => { + beforeEach(async () => { await client.connect() - sub = mockSubscription('stream1', () => {}).once('subscribed', () => done()) + sub = await mockSubscription('stream1', () => {}) }) it('reports InvalidJsonErrors to subscriptions', (done) => { @@ -751,14 +759,12 @@ describe('StreamrClient', () => { client.onError = jest.fn() const testError = new Error('This is a test error message, ignore') - client.once('error', async (err) => { + client.once('error', (err) => { + errors.pop() expect(err.message).toMatch(testError.message) expect(client.onError).toHaveBeenCalled() done() }) - client.once('error', () => { - errors.pop() - }) connection.emit('error', testError) }) }) @@ -847,81 +853,71 @@ describe('StreamrClient', () => { requestId, }) connection.emitMessage(resendResponse) - client.connection.socket.close() + connection.socket.close() await client.connect() expect(requests.filter((req) => req.type === ControlMessage.TYPES.SubscribeRequest)).toHaveLength(0) }) }) describe('subscribe()', () => { - it('should call client.connect() if autoConnect is set to true', (done) => { + it('should connect if autoConnect is set to true', async () => { client.options.autoConnect = true - client.once('connected', done) - - client.subscribe('stream1', () => {}) + await mockSubscription('stream1', () => {}) }) describe('when connected', () => { beforeEach(() => client.connect()) it('throws an error if no options are given', () => { - expect(() => { + expect(() => ( client.subscribe(undefined, () => {}) - }).toThrow() + )).rejects.toThrow() }) it('throws an error if options is wrong type', () => { - expect(() => { + expect(() => ( client.subscribe(['streamId'], () => {}) - }).toThrow() + )).rejects.toThrow() }) it('throws an error if no callback is given', () => { - expect(() => { + expect(() => ( client.subscribe('stream1') - }).toThrow() + )).rejects.toThrow() }) - it('sends a subscribe request', (done) => { - const sub = mockSubscription('stream1', () => {}) - sub.once('subscribed', () => { - const lastRequest = requests[requests.length - 1] - expect(lastRequest).toEqual(new SubscribeRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId: lastRequest.requestId, - sessionToken: 'session-token' - })) - done() - }) + it('sends a subscribe request', async () => { + const sub = await mockSubscription('stream1', () => {}) + const lastRequest = requests[requests.length - 1] + expect(lastRequest).toEqual(new SubscribeRequest({ + streamId: sub.streamId, + streamPartition: sub.streamPartition, + requestId: lastRequest.requestId, + sessionToken: 'session-token' + })) }) - it('sends a subscribe request for a given partition', (done) => { - const sub = mockSubscription({ + it('sends a subscribe request for a given partition', async () => { + const sub = await mockSubscription({ stream: 'stream1', partition: 5, - }, () => {}).once('subscribed', () => { - const lastRequest = requests[requests.length - 1] - expect(lastRequest).toEqual(new SubscribeRequest({ - streamId: sub.streamId, - streamPartition: 5, - requestId: lastRequest.requestId, - sessionToken, - })) - done() - }) + }, () => {}) + const lastRequest = requests[requests.length - 1] + expect(lastRequest).toEqual(new SubscribeRequest({ + streamId: sub.streamId, + streamPartition: 5, + requestId: lastRequest.requestId, + sessionToken, + })) }) it('sends subscribe request for each subscribed partition', async () => { const tasks = [] for (let i = 0; i < 3; i++) { - tasks.push(new Promise((resolve) => { - const s = mockSubscription({ - stream: 'stream1', - partition: i, - }, () => {}) - .once('subscribed', () => resolve(s)) - })) + tasks.push(mockSubscription({ + stream: 'stream1', + partition: i, + }, () => {})) } const subs = await Promise.all(tasks) @@ -949,11 +945,9 @@ describe('StreamrClient', () => { }) it('sends just one subscribe request to server even if there are multiple subscriptions for same stream', async () => { - const sub = mockSubscription('stream1', () => {}) - const sub2 = mockSubscription('stream1', () => {}) - await Promise.all([ - new Promise((resolve) => sub.once('subscribed', resolve)), - new Promise((resolve) => sub2.once('subscribed', resolve)) + const [sub, sub2] = await Promise.all([ + mockSubscription('stream1', () => {}), + mockSubscription('stream1', () => {}) ]) expect(requests).toHaveLength(1) const request = requests[0] @@ -969,9 +963,9 @@ describe('StreamrClient', () => { }) describe('with resend options', () => { - it('supports resend.from', (done) => { + it('supports resend.from', async () => { const ref = new MessageRef(5, 0) - const sub = mockSubscription({ + const sub = await mockSubscription({ stream: 'stream1', resend: { from: { @@ -981,60 +975,54 @@ describe('StreamrClient', () => { publisherId: 'publisherId', }, }, () => {}) - sub.once('subscribed', async () => { - await wait(200) - const lastRequest = requests[requests.length - 1] - expect(lastRequest).toEqual(new ResendFromRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId: lastRequest.requestId, - publisherId: 'publisherId', - fromMsgRef: ref, - sessionToken, - })) - const streamMessage = getStreamMessage(sub.streamId, {}) - connection.emitMessage(new UnicastMessage({ - requestId: lastRequest.requestId, - streamMessage, - })) - // TODO validate message - await wait(STORAGE_DELAY + 200) - sub.stop() - done() - }) + await wait(200) + const lastRequest = requests[requests.length - 1] + expect(lastRequest).toEqual(new ResendFromRequest({ + streamId: sub.streamId, + streamPartition: sub.streamPartition, + requestId: lastRequest.requestId, + publisherId: 'publisherId', + fromMsgRef: ref, + sessionToken, + })) + const streamMessage = getStreamMessage(sub.streamId, {}) + connection.emitMessage(new UnicastMessage({ + requestId: lastRequest.requestId, + streamMessage, + })) + // TODO validate message + await wait(STORAGE_DELAY + 200) + sub.stop() }, STORAGE_DELAY + 1000) - it('supports resend.last', (done) => { - const sub = mockSubscription({ + it('supports resend.last', async () => { + const sub = await mockSubscription({ stream: 'stream1', resend: { last: 5, }, }, () => {}) - sub.once('subscribed', async () => { - await wait(200) - const lastRequest = requests[requests.length - 1] - expect(lastRequest).toEqual(new ResendLastRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId: lastRequest.requestId, - numberLast: 5, - sessionToken, - })) - const streamMessage = getStreamMessage(sub.streamId, {}) - connection.emitMessage(new UnicastMessage({ - requestId: lastRequest.requestId, - streamMessage, - })) - // TODO validate message - await wait(STORAGE_DELAY + 200) - sub.stop() - done() - }) + await wait(200) + const lastRequest = requests[requests.length - 1] + expect(lastRequest).toEqual(new ResendLastRequest({ + streamId: sub.streamId, + streamPartition: sub.streamPartition, + requestId: lastRequest.requestId, + numberLast: 5, + sessionToken, + })) + const streamMessage = getStreamMessage(sub.streamId, {}) + connection.emitMessage(new UnicastMessage({ + requestId: lastRequest.requestId, + streamMessage, + })) + // TODO validate message + await wait(STORAGE_DELAY + 200) + sub.stop() }, STORAGE_DELAY + 1000) it('sends a ResendLastRequest if no StreamMessage received and a ResendResponseNoResend received', async () => { - const sub = client.subscribe({ + const t = client.subscribe({ stream: 'stream1', resend: { last: 5, @@ -1045,7 +1033,7 @@ describe('StreamrClient', () => { await wait() if (request.type === ControlMessage.TYPES.SubscribeRequest) { connection.emitMessage(new SubscribeResponse({ - streamId: sub.streamId, + streamId: request.streamId, requestId: request.requestId, streamPartition: request.streamPartition, })) @@ -1053,8 +1041,8 @@ describe('StreamrClient', () => { if (request.type === ControlMessage.TYPES.ResendLastRequest) { const resendResponse = new ResendResponseNoResend({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, + streamId: request.streamId, + streamPartition: request.streamPartition, requestId: request.requestId }) connection.emitMessage(resendResponse) @@ -1062,6 +1050,7 @@ describe('StreamrClient', () => { } await wait(STORAGE_DELAY + 200) + const sub = await t sub.stop() expect(requests).toHaveLength(2) const lastRequest = requests[requests.length - 1] @@ -1075,7 +1064,7 @@ describe('StreamrClient', () => { }, STORAGE_DELAY + 1000) it('throws if multiple resend options are given', () => { - expect(() => { + expect(() => ( client.subscribe({ stream: 'stream1', resend: { @@ -1086,95 +1075,85 @@ describe('StreamrClient', () => { last: 5, }, }, () => {}) - }).toThrow() + )).rejects.toThrow() }) }) describe('Subscription event handling', () => { describe('gap', () => { - it('sends resend request', (done) => { - const sub = mockSubscription('streamId', () => {}) - sub.once('subscribed', async () => { - await wait() - const fromRef = new MessageRef(1, 0) - const toRef = new MessageRef(5, 0) - - const fromRefObject = { - timestamp: fromRef.timestamp, - sequenceNumber: fromRef.sequenceNumber, - } - const toRefObject = { - timestamp: toRef.timestamp, - sequenceNumber: toRef.sequenceNumber, - } - sub.emit('gap', fromRefObject, toRefObject, 'publisherId', 'msgChainId') - await wait(100) - - expect(requests).toHaveLength(2) - const lastRequest = requests[requests.length - 1] - expect(lastRequest).toEqual(new ResendRangeRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId: lastRequest.requestId, - fromMsgRef: fromRef, - toMsgRef: toRef, - msgChainId: lastRequest.msgChainId, - publisherId: lastRequest.publisherId, - sessionToken, - })) - done() - }) + it('sends resend request', async () => { + const sub = await mockSubscription('streamId', () => {}) + const fromRef = new MessageRef(1, 0) + const toRef = new MessageRef(5, 0) + + const fromRefObject = { + timestamp: fromRef.timestamp, + sequenceNumber: fromRef.sequenceNumber, + } + const toRefObject = { + timestamp: toRef.timestamp, + sequenceNumber: toRef.sequenceNumber, + } + sub.emit('gap', fromRefObject, toRefObject, 'publisherId', 'msgChainId') + await wait(100) + + expect(requests).toHaveLength(2) + const lastRequest = requests[requests.length - 1] + expect(lastRequest).toEqual(new ResendRangeRequest({ + streamId: sub.streamId, + streamPartition: sub.streamPartition, + requestId: lastRequest.requestId, + fromMsgRef: fromRef, + toMsgRef: toRef, + msgChainId: lastRequest.msgChainId, + publisherId: lastRequest.publisherId, + sessionToken, + })) }) - it('does not send another resend request while resend is in progress', (done) => { - const sub = mockSubscription('streamId', () => {}) - sub.once('subscribed', async () => { - await wait() - const fromRef = new MessageRef(1, 0) - const toRef = new MessageRef(5, 0) - const fromRefObject = { - timestamp: fromRef.timestamp, - sequenceNumber: fromRef.sequenceNumber, - } - const toRefObject = { - timestamp: toRef.timestamp, - sequenceNumber: toRef.sequenceNumber, - } - sub.emit('gap', fromRefObject, toRefObject, 'publisherId', 'msgChainId') - sub.emit('gap', fromRefObject, { - timestamp: 10, - sequenceNumber: 0, - }, 'publisherId', 'msgChainId') - await wait() - expect(requests).toHaveLength(2) - const lastRequest = requests[requests.length - 1] - expect(lastRequest).toEqual(new ResendRangeRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId: lastRequest.requestId, - fromMsgRef: fromRef, - toMsgRef: toRef, - msgChainId: lastRequest.msgChainId, - publisherId: lastRequest.publisherId, - sessionToken, - })) - done() - }) + it('does not send another resend request while resend is in progress', async () => { + const sub = await mockSubscription('streamId', () => {}) + const fromRef = new MessageRef(1, 0) + const toRef = new MessageRef(5, 0) + const fromRefObject = { + timestamp: fromRef.timestamp, + sequenceNumber: fromRef.sequenceNumber, + } + const toRefObject = { + timestamp: toRef.timestamp, + sequenceNumber: toRef.sequenceNumber, + } + sub.emit('gap', fromRefObject, toRefObject, 'publisherId', 'msgChainId') + sub.emit('gap', fromRefObject, { + timestamp: 10, + sequenceNumber: 0, + }, 'publisherId', 'msgChainId') + await wait() + expect(requests).toHaveLength(2) + const lastRequest = requests[requests.length - 1] + expect(lastRequest).toEqual(new ResendRangeRequest({ + streamId: sub.streamId, + streamPartition: sub.streamPartition, + requestId: lastRequest.requestId, + fromMsgRef: fromRef, + toMsgRef: toRef, + msgChainId: lastRequest.msgChainId, + publisherId: lastRequest.publisherId, + sessionToken, + })) }) }) describe('done', () => { - it('unsubscribes', (done) => { - const sub = mockSubscription('stream1', () => {}) + it('unsubscribes', async (done) => { + const sub = await mockSubscription('stream1', () => {}) client.subscriber.unsubscribe = async (unsub) => { expect(sub).toBe(unsub) done() } - sub.once('subscribed', async () => { - await wait() - sub.emit('done') - }) + await wait() + sub.emit('done') }) }) }) @@ -1184,12 +1163,11 @@ describe('StreamrClient', () => { describe('unsubscribe()', () => { // Before each, client is connected and subscribed let sub - beforeEach(async (done) => { + beforeEach(async () => { await client.connect() - sub = mockSubscription('stream1', () => { + sub = await mockSubscription('stream1', () => { errors.push(new Error('should not fire message handler')) }) - sub.once('subscribed', () => done()) }) it('sends an unsubscribe request', async () => { @@ -1205,7 +1183,7 @@ describe('StreamrClient', () => { }) it('does not send unsubscribe request if there are other subs remaining for the stream', async () => { - client.subscribe({ + await mockSubscription({ stream: sub.streamId, }, () => {}) @@ -1213,23 +1191,36 @@ describe('StreamrClient', () => { expect(requests).toHaveLength(1) }) - it('sends unsubscribe request when the last subscription is unsubscribed', (done) => { - const sub2 = client.subscribe({ - stream: sub.streamId, - }, () => {}) + it('sends unsubscribe request when the last subscription is unsubscribed', async () => { + const sub2 = await mockSubscription(sub.streamId, () => {}) - sub2.once('subscribed', async () => { - await client.unsubscribe(sub) - await client.unsubscribe(sub2) - const lastRequest = requests[requests.length - 1] - expect(lastRequest).toEqual(new UnsubscribeRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId: lastRequest.requestId, - sessionToken, - })) - done() - }) + await client.unsubscribe(sub) + await client.unsubscribe(sub2) + const lastRequest = requests[requests.length - 1] + expect(lastRequest).toEqual(new UnsubscribeRequest({ + streamId: sub.streamId, + streamPartition: sub.streamPartition, + requestId: lastRequest.requestId, + sessionToken, + })) + }) + + it('sends only a single unsubscribe request when the last subscription is unsubscribed', async () => { + const sub2 = await mockSubscription(sub.streamId, () => {}) + requests = [] + await Promise.all([ + client.unsubscribe(sub), + client.unsubscribe(sub2) + ]) + expect(requests).toHaveLength(1) + const lastRequest = requests[requests.length - 1] + + expect(lastRequest).toEqual(new UnsubscribeRequest({ + streamId: sub.streamId, + streamPartition: sub.streamPartition, + requestId: lastRequest.requestId, + sessionToken, + })) }) it('does not send an unsubscribe request again if unsubscribe is called multiple times', async () => { @@ -1399,8 +1390,7 @@ describe('StreamrClient', () => { }) it('does not reset subscriptions', async () => { - const sub = mockSubscription('stream1', () => {}) - await new Promise((resolve) => sub.once('subscribed', resolve)) + const sub = await mockSubscription('stream1', () => {}) await client.pause() expect(client.getSubscriptions(sub.streamId)).toEqual([sub]) }) From 455eef27ce0715fedb7cec948067575da4f248bf Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Sat, 12 Sep 2020 13:12:45 -0400 Subject: [PATCH 050/517] Fail _request call if unsubscribe/subscribe request fails to send. --- src/Subscriber.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Subscriber.js b/src/Subscriber.js index 21ab24325..ea66303a9 100644 --- a/src/Subscriber.js +++ b/src/Subscriber.js @@ -366,7 +366,7 @@ export default class Subscriber { this.debug('_requestSubscribe: subscribing client: %o', request) await this.client.send(request).catch((err) => { sub.setState(Subscription.State.unsubscribed) - const error = new Error(`Failed to sendnsubscribe request: ${err.stack}`) + const error = new Error(`Failed to send subscribe request: ${err.stack}`) this.onErrorEmit(error) throw error }) @@ -381,9 +381,8 @@ export default class Subscriber { }) await this.client.connection.send(unsubscribeRequest).catch((err) => { sub.setState(Subscription.State.subscribed) - const error = new Error(`Failed to send unsubscribe request: ${err.stack}`) - this.onErrorEmit(error) - throw error + this.client.emit(new Error(`Failed to send unsubscribe request: ${err.stack}`)) + throw err }) return this._checkAutoDisconnect() } From f3e8cee6a8f1c3c91d1a7ebd85d0c362b3d85073 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Sat, 12 Sep 2020 13:51:01 -0400 Subject: [PATCH 051/517] Fix missing import in resend test. --- test/integration/Resends.test.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/integration/Resends.test.js b/test/integration/Resends.test.js index 7e84cf595..e980fc0f0 100644 --- a/test/integration/Resends.test.js +++ b/test/integration/Resends.test.js @@ -1,3 +1,4 @@ +import { wait, waitForCondition, waitForEvent } from 'streamr-test-utils' import Debug from 'debug' import { uid } from '../utils' @@ -5,8 +6,6 @@ import StreamrClient from '../../src' import config from './config' -const { wait, waitForCondition } = require('streamr-test-utils') - const createClient = (opts = {}) => new StreamrClient({ apiKey: 'tester1-api-key', autoConnect: false, From 15e7bb865df8cdc9646d67de8416c756692ab9ca Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 15 Sep 2020 11:16:15 -0400 Subject: [PATCH 052/517] Fix browser tests. --- test/browser/browser.html | 48 +++++++++++++++++---------------------- 1 file changed, 21 insertions(+), 27 deletions(-) diff --git a/test/browser/browser.html b/test/browser/browser.html index 93e811065..351715669 100644 --- a/test/browser/browser.html +++ b/test/browser/browser.html @@ -40,32 +40,33 @@ const resetResults = () => $('#result').html('') +client.on('error', (err) => { + console.error(err) + $('#result').html('Error: ' + err) +}) + $('#connect').on('click', async () => { resetResults() await client.connect() $('#result').html(client.connection.getState()) }) -$('#create').on('click', () => { +$('#create').on('click', async () => { resetResults() - client.createStream({ + stream = await client.createStream({ name: streamName - }).then((newStream) => { - stream = newStream - $('#result').html(stream.name) }) + $('#result').html(stream.name) }) -$('#subscribe').on('click', () => { +$('#subscribe').on('click', async () => { resetResults() - const sub = client.subscribe({ - stream: stream.id - }, - (message, metadata) => { - messages.push(message) - } - ) - sub.on('subscribed', () => $('#result').html('subscribed')) + await client.subscribe({ + stream: stream.id + }, (message, metadata) => { + messages.push(message) + }) + $('#result').html('subscribed') }) $('#publish').on('click', async () => { @@ -84,15 +85,13 @@ messages = [] const sub = await client.resend({ - stream: stream.id, - resend: { - last: 10, - }, + stream: stream.id, + resend: { + last: 10, }, - (message) => { - messages.push(message) - } - ) + }, (message) => { + messages.push(message) + }) sub.on('resent', () => { $('#result').html('Resend: ' + JSON.stringify(messages)) @@ -103,10 +102,5 @@ await client.disconnect() $('#result').html(client.connection.getState()) }) - -client.on('error', (err) => { - console.error(err) - $('#result').html('Error: ' + err) -}) From 5174c566606076af7591add245340dcdff1badc7 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 15 Sep 2020 14:45:24 -0400 Subject: [PATCH 053/517] Clean up unused function in test. --- test/unit/StreamrClient.test.js | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index 75bfd352a..53825e652 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -41,36 +41,6 @@ describe('StreamrClient', () => { const streamPartition = 0 const sessionToken = 'session-token' - function setupSubscription( - streamId, emitSubscribed = true, subscribeOptions = {}, handler = sinon.stub(), - expectSubscribeRequest = !client.getSubscriptions(streamId).length, - ) { - expect(client.isConnected()).toBeTruthy() - const requestId = uid('request') - - if (expectSubscribeRequest) { - connection.expect(new SubscribeRequest({ - requestId, - streamId, - streamPartition, - sessionToken, - })) - } - const sub = client.subscribe({ - stream: streamId, - ...subscribeOptions, - }, handler) - - if (emitSubscribed) { - connection.emitMessage(new SubscribeResponse({ - streamId: sub.streamId, - requestId, - streamPartition, - })) - } - return sub - } - function getStreamMessage(streamId = 'stream1', content = {}, publisherId = '') { const timestamp = Date.now() return new StreamMessage({ From 6e6f206e2ee89b5de5634474a79497b7cae16a47 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 15 Sep 2020 18:36:36 -0400 Subject: [PATCH 054/517] Refactor & co-locate publish code. --- src/MessageCreationUtil.js | 178 ----------------------- src/Publisher.js | 192 +++++++++++++++++++++++-- src/rest/StreamEndpoints.js | 1 + src/utils.js | 49 +++++++ test/integration/StreamrClient.test.js | 9 +- test/unit/MessageCreationUtil.test.js | 46 +++--- test/unit/StreamrClient.test.js | 2 +- 7 files changed, 264 insertions(+), 213 deletions(-) delete mode 100644 src/MessageCreationUtil.js diff --git a/src/MessageCreationUtil.js b/src/MessageCreationUtil.js deleted file mode 100644 index 17e7c2567..000000000 --- a/src/MessageCreationUtil.js +++ /dev/null @@ -1,178 +0,0 @@ -import crypto from 'crypto' - -import Receptacle from 'receptacle' -import randomstring from 'randomstring' -import { MessageLayer } from 'streamr-client-protocol' -import { ethers } from 'ethers' - -import Stream from './rest/domain/Stream' -import InvalidMessageTypeError from './errors/InvalidMessageTypeError' - -const { StreamMessage, MessageID, MessageRef } = MessageLayer - -export default class MessageCreationUtil { - constructor(auth, signer, getUserInfo, getStreamFunction) { - this.auth = auth - this._signer = signer - this.getUserInfo = getUserInfo - this.getStreamFunction = getStreamFunction - this.cachedStreams = new Receptacle({ - max: 10000, - }) - this.publishedStreams = {} - this.msgChainId = randomstring.generate(20) - this.cachedHashes = {} - } - - stop() { - this.cachedStreams.clear() - } - - async getUsername() { - if (!this.usernamePromise) { - // In the edge case where StreamrClient.auth.apiKey is an anonymous key, userInfo.id is that anonymous key - this.usernamePromise = this.getUserInfo().then((userInfo) => userInfo.username || userInfo.id) - } - return this.usernamePromise - } - - async getStream(streamId) { - if (!this.cachedStreams.get(streamId)) { - const streamPromise = this.getStreamFunction(streamId).then((stream) => ({ - id: stream.id, - partitions: stream.partitions, - })) - const success = this.cachedStreams.set(streamId, streamPromise, { - ttl: 30 * 60 * 1000, // 30 minutes - refresh: true, // reset ttl on access - }) - if (!success) { - console.warn(`Could not store stream with id ${streamId} in local cache.`) - return streamPromise - } - } - return this.cachedStreams.get(streamId) - } - - async getPublisherId() { - if (!this.publisherId) { - if (this.auth.privateKey !== undefined) { - this.publisherId = ethers.utils.computeAddress(this.auth.privateKey).toLowerCase() - } else if (this.auth.provider !== undefined) { - const provider = new ethers.providers.Web3Provider(this.auth.provider) - this.publisherId = provider.getSigner().address.toLowerCase() - } else if (this.auth.apiKey !== undefined) { - const hexString = ethers.utils.hexlify(Buffer.from(await this.getUsername(), 'utf8')) - this.publisherId = ethers.utils.sha256(hexString) - } else if (this.auth.username !== undefined) { - const hexString = ethers.utils.hexlify(Buffer.from(this.auth.username, 'utf8')) - this.publisherId = ethers.utils.sha256(hexString) - } else if (this.auth.sessionToken !== undefined) { - const hexString = ethers.utils.hexlify(Buffer.from(await this.getUsername(), 'utf8')) - this.publisherId = ethers.utils.sha256(hexString) - } else { - throw new Error('Need either "privateKey", "provider", "apiKey", "username"+"password" or "sessionToken" to derive the publisher Id.') - } - } - return this.publisherId - } - - getNextSequenceNumber(key, timestamp) { - if (timestamp !== this.getPrevTimestamp(key)) { - return 0 - } - return this.getPrevSequenceNumber(key) + 1 - } - - getPrevMsgRef(key) { - const prevTimestamp = this.getPrevTimestamp(key) - if (!prevTimestamp) { - return null - } - const prevSequenceNumber = this.getPrevSequenceNumber(key) - return new MessageRef(prevTimestamp, prevSequenceNumber) - } - - getPrevTimestamp(key) { - return this.publishedStreams[key].prevTimestamp - } - - getPrevSequenceNumber(key) { - return this.publishedStreams[key].prevSequenceNumber - } - - async createStreamMessage(streamObjectOrId, data, timestamp = Date.now(), partitionKey = null) { - // Validate data - if (typeof data !== 'object') { - throw new Error(`Message data must be an object! Was: ${data}`) - } - - const stream = (streamObjectOrId instanceof Stream) ? streamObjectOrId : await this.getStream(streamObjectOrId) - const streamPartition = this.computeStreamPartition(stream.partitions, partitionKey) - const publisherId = await this.getPublisherId() - const [messageId, prevMsgRef] = this.createMsgIdAndPrevRef(stream.id, streamPartition, timestamp, publisherId) - - const streamMessage = new StreamMessage({ - messageId, - prevMsgRef, - content: data, - messageType: StreamMessage.MESSAGE_TYPES.MESSAGE, - }) - - if (this._signer) { - await this._signer.signStreamMessage(streamMessage) - } - return streamMessage - } - - createMsgIdAndPrevRef(streamId, streamPartition, timestamp, publisherId) { - const key = streamId + streamPartition - if (!this.publishedStreams[key]) { - this.publishedStreams[key] = { - prevTimestamp: null, - prevSequenceNumber: 0, - } - } - - const sequenceNumber = this.getNextSequenceNumber(key, timestamp) - const messageId = new MessageID(streamId, streamPartition, timestamp, sequenceNumber, publisherId, this.msgChainId) - const prevMsgRef = this.getPrevMsgRef(key) - this.publishedStreams[key].prevTimestamp = timestamp - this.publishedStreams[key].prevSequenceNumber = sequenceNumber - return [messageId, prevMsgRef] - } - - createDefaultMsgIdAndPrevRef(streamId, publisherId) { - return this.createMsgIdAndPrevRef(streamId, 0, Date.now(), publisherId) - } - - static getErrorCodeFromError(error) { - if (error instanceof InvalidMessageTypeError) { - return 'INVALID_MESSAGE_TYPE' - } - return 'UNEXPECTED_ERROR' - } - - hash(stringToHash) { - if (this.cachedHashes[stringToHash] === undefined) { - this.cachedHashes[stringToHash] = crypto.createHash('md5').update(stringToHash).digest() - } - return this.cachedHashes[stringToHash] - } - - computeStreamPartition(partitionCount, partitionKey) { - if (!partitionCount) { - throw new Error('partitionCount is falsey!') - } else if (partitionCount === 1) { - // Fast common case - return 0 - } else if (partitionKey) { - const buffer = this.hash(partitionKey) - const intHash = buffer.readInt32LE() - return Math.abs(intHash) % partitionCount - } else { - // Fallback to random partition if no key - return Math.floor(Math.random() * partitionCount) - } - } -} diff --git a/src/Publisher.js b/src/Publisher.js index a13610454..3551736f1 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -1,10 +1,15 @@ -import once from 'once' -import { ControlLayer } from 'streamr-client-protocol' +import crypto from 'crypto' + +import { ControlLayer, MessageLayer } from 'streamr-client-protocol' +import randomstring from 'randomstring' +import { ethers } from 'ethers' import Signer from './Signer' import Stream from './rest/domain/Stream' import FailedToPublishError from './errors/FailedToPublishError' -import MessageCreationUtil from './MessageCreationUtil' +import { AsyncCacheMap, AsyncCacheFn } from './utils' + +const { StreamMessage, MessageID, MessageRef } = MessageLayer function getStreamId(streamObjectOrId) { if (streamObjectOrId instanceof Stream) { @@ -18,12 +23,168 @@ function getStreamId(streamObjectOrId) { throw new Error(`First argument must be a Stream object or the stream id! Was: ${streamObjectOrId}`) } +class MessageChainer { + constructor() { + this.msgChainId = randomstring.generate(20) + this.publishedStreams = {} + } + + create(streamId, streamPartition, timestamp, publisherId) { + const key = streamId + streamPartition + if (!this.publishedStreams[key]) { + this.publishedStreams[key] = { + prevTimestamp: null, + prevSequenceNumber: 0, + } + } + + const sequenceNumber = this.getNextSequenceNumber(key, timestamp) + const messageId = new MessageID(streamId, streamPartition, timestamp, sequenceNumber, publisherId, this.msgChainId) + const prevMsgRef = this.getPrevMsgRef(key) + this.publishedStreams[key].prevTimestamp = timestamp + this.publishedStreams[key].prevSequenceNumber = sequenceNumber + return [messageId, prevMsgRef] + } + + getPrevMsgRef(key) { + const prevTimestamp = this.getPrevTimestamp(key) + if (!prevTimestamp) { + return null + } + const prevSequenceNumber = this.getPrevSequenceNumber(key) + return new MessageRef(prevTimestamp, prevSequenceNumber) + } + + getNextSequenceNumber(key, timestamp) { + if (timestamp !== this.getPrevTimestamp(key)) { + return 0 + } + return this.getPrevSequenceNumber(key) + 1 + } + + getPrevTimestamp(key) { + return this.publishedStreams[key] && this.publishedStreams[key].prevTimestamp + } + + getPrevSequenceNumber(key) { + return this.publishedStreams[key].prevSequenceNumber + } +} + +export class MessageCreationUtil { + constructor(client) { + this.client = client + this.msgChainer = new MessageChainer() + + this.streamPartitionCache = new AsyncCacheMap(async (streamId) => { + const { partitions } = await this.client.getStream(streamId) + return partitions + }) + this.getUserInfo = AsyncCacheFn(this.getUserInfo.bind(this)) + + this.getPublisherId = AsyncCacheFn(this.getPublisherId.bind(this)) + this.cachedHashes = {} + } + + stop() { + this.msgChainer = new MessageChainer() + this.getUserInfo.stop() + this.getPublisherId.stop() + this.streamPartitionCache.stop() + } + + async getPublisherId() { + const { auth = {} } = this.client.options + if (auth.privateKey !== undefined) { + return ethers.utils.computeAddress(auth.privateKey).toLowerCase() + } + + if (auth.provider !== undefined) { + const provider = new ethers.providers.Web3Provider(auth.provider) + return provider.getSigner().address.toLowerCase() + } + + const username = auth.username || await this.getUsername() + + if (username !== undefined) { + const hexString = ethers.utils.hexlify(Buffer.from(username, 'utf8')) + return ethers.utils.sha256(hexString) + } + + throw new Error('Need either "privateKey", "provider", "apiKey", "username"+"password" or "sessionToken" to derive the publisher Id.') + } + + /* cached remote call */ + async getUserInfo() { + return this.client.getUserInfo() + } + + async getUsername() { + return this.getUserInfo().then((userInfo) => ( + userInfo.username + || userInfo.id // In the edge case where StreamrClient.auth.apiKey is an anonymous key, userInfo.id is that anonymous key + )) + } + + async getStreamPartitions(streamObjectOrId) { + if (streamObjectOrId && streamObjectOrId.partitions != null) { + return streamObjectOrId.partitions + } + const streamId = getStreamId(streamObjectOrId) + return this.streamPartitionCache.load(streamId) + } + + hash(stringToHash) { + if (this.cachedHashes[stringToHash] === undefined) { + this.cachedHashes[stringToHash] = crypto.createHash('md5').update(stringToHash).digest() + } + return this.cachedHashes[stringToHash] + } + + computeStreamPartition(partitionCount, partitionKey) { + if (!partitionCount) { + throw new Error('partitionCount is falsey!') + } else if (partitionCount === 1) { + // Fast common case + return 0 + } else if (partitionKey) { + const buffer = this.hash(partitionKey) + const intHash = buffer.readInt32LE() + return Math.abs(intHash) % partitionCount + } else { + // Fallback to random partition if no key + return Math.floor(Math.random() * partitionCount) + } + } + + async createStreamMessage(streamObjectOrId, { data, timestamp, partitionKey } = {}) { + // Validate data + if (typeof data !== 'object') { + throw new Error(`Message data must be an object! Was: ${data}`) + } + + const streamId = getStreamId(streamObjectOrId) + const [streamPartitions, publisherId] = await Promise.all([ + this.getStreamPartitions(streamObjectOrId), + this.getPublisherId(), + ]) + + const streamPartition = this.computeStreamPartition(streamPartitions, partitionKey) + const [messageId, prevMsgRef] = this.msgChainer.create(streamId, streamPartition, timestamp, publisherId) + + return new StreamMessage({ + messageId, + prevMsgRef, + content: data, + }) + } +} + export default class Publisher { constructor(client) { this.client = client this.debug = client.debug.extend('Publisher') - this.publishQueue = [] this.signer = Signer.createSigner({ ...client.options.auth, debug: client.debug, @@ -34,15 +195,13 @@ export default class Publisher { if (client.session.isUnauthenticated()) { this.msgCreationUtil = null } else { - this.msgCreationUtil = new MessageCreationUtil( - client.options.auth, this.signer, once(() => client.getUserInfo()), - (streamId) => client.getStream(streamId).catch(this.onErrorEmit) - ) + this.msgCreationUtil = new MessageCreationUtil(this.client) } } async publish(...args) { this.debug('publish()') + return this._publish(...args).catch((err) => { this.onErrorEmit(err) throw err @@ -53,15 +212,21 @@ export default class Publisher { if (this.client.session.isUnauthenticated()) { throw new Error('Need to be authenticated to publish.') } - // Validate streamObjectOrId - const streamId = getStreamId(streamObjectOrId) const timestampAsNumber = timestamp instanceof Date ? timestamp.getTime() : new Date(timestamp).getTime() const [sessionToken, streamMessage] = await Promise.all([ this.client.session.getSessionToken(), - this.msgCreationUtil.createStreamMessage(streamObjectOrId, data, timestampAsNumber, partitionKey), + this.msgCreationUtil.createStreamMessage(streamObjectOrId, { + data, + timestamp: timestampAsNumber, + partitionKey + }), ]) + if (this.signer) { + await this.signer.signStreamMessage(streamMessage) + } + const requestId = this.client.resender.resendUtil.generateRequestId() const request = new ControlLayer.PublishRequest({ streamMessage, @@ -71,6 +236,7 @@ export default class Publisher { try { await this.client.send(request) } catch (err) { + const streamId = getStreamId(streamObjectOrId) throw new FailedToPublishError( streamId, data, @@ -85,8 +251,6 @@ export default class Publisher { } stop() { - if (this.msgCreationUtil) { - this.msgCreationUtil.stop() - } + return this.msgCreationUtil.stop() } } diff --git a/src/rest/StreamEndpoints.js b/src/rest/StreamEndpoints.js index 39a9fec7f..064504814 100644 --- a/src/rest/StreamEndpoints.js +++ b/src/rest/StreamEndpoints.js @@ -132,6 +132,7 @@ export async function isStreamPublisher(streamId, ethAddress) { await authFetch(url, this.session) return true } catch (e) { + this.debug(e) if (e.response && e.response.status === 404) { return false } diff --git a/src/utils.js b/src/utils.js index ec43d0da0..75a34e0b9 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,3 +1,4 @@ +import Receptacle from 'receptacle' import { v4 as uuidv4 } from 'uuid' import uniqueId from 'lodash.uniqueid' @@ -39,3 +40,51 @@ export function waitFor(emitter, event) { export const getEndpointUrl = (baseUrl, ...pathParts) => { return baseUrl + '/' + pathParts.map((part) => encodeURIComponent(part)).join('/') } + +export class AsyncCacheMap { + /* eslint-disable object-curly-newline */ + constructor(fn, { + max = 10000, + ttl = 30 * 60 * 1000, // 30 minutes + refresh = true, // reset ttl on access + } = {}) { + /* eslint-disable-next-line object-curly-newline */ + this.ttl = ttl + this.refresh = refresh + this.fn = fn + this.cache = new Receptacle({ + max, + }) + } + + load(id, { ttl = this.ttl, refresh = this.refresh, } = {}) { + if (!this.cache.get(id)) { + const promise = this.fn(id) + const success = this.cache.set(id, promise, { + ttl, + refresh, + }) + if (!success) { + console.warn(`Could not store ${id} in local cache.`) + return promise + } + } + return this.cache.get(id) + } + + stop() { + this.cache.clear() + } +} + +export function AsyncCacheFn(fn, options) { + const cache = new AsyncCacheMap(fn, options) + const cacheFn = async (opts) => { + return cache.load('value', opts) + } + cacheFn.cache = cache + cacheFn.stop = () => { + return cache.stop() + } + return cacheFn +} diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index 55d68e9e6..5d8873bad 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -759,9 +759,6 @@ describe('StreamrClient', () => { client = createClient() await client.connect() stream = await createStream() - const publisherId = await client.getPublisherId() - const res = await client.isStreamPublisher(stream.id, publisherId.toLowerCase()) - expect(res).toBe(true) expect(onError).toHaveBeenCalledTimes(0) }) @@ -785,6 +782,12 @@ describe('StreamrClient', () => { } }) + it('is stream publisher', async () => { + const publisherId = await client.getPublisherId() + const res = await client.isStreamPublisher(stream.id, publisherId) + expect(res).toBe(true) + }) + describe('Pub/Sub', () => { it('client.publish does not error', async (done) => { await client.publish(stream.id, { diff --git a/test/unit/MessageCreationUtil.test.js b/test/unit/MessageCreationUtil.test.js index 0616e8787..82eb18f46 100644 --- a/test/unit/MessageCreationUtil.test.js +++ b/test/unit/MessageCreationUtil.test.js @@ -2,7 +2,7 @@ import sinon from 'sinon' import { ethers } from 'ethers' import { MessageLayer } from 'streamr-client-protocol' -import MessageCreationUtil from '../../src/MessageCreationUtil' +import { MessageCreationUtil } from '../../src/Publisher' import Stream from '../../src/rest/domain/Stream' const { StreamMessage, MessageID, MessageRef } = MessageLayer @@ -23,7 +23,7 @@ describe('MessageCreationUtil', () => { username: 'username', }), } - const msgCreationUtil = new MessageCreationUtil(client.options.auth, undefined, client.getUserInfo) + const msgCreationUtil = new MessageCreationUtil(client) const publisherId = await msgCreationUtil.getPublisherId() expect(publisherId).toBe(wallet.address.toLowerCase()) }) @@ -39,7 +39,7 @@ describe('MessageCreationUtil', () => { username: 'username', }), } - const msgCreationUtil = new MessageCreationUtil(client.options.auth, undefined, client.getUserInfo) + const msgCreationUtil = new MessageCreationUtil(client) const publisherId = await msgCreationUtil.getPublisherId() expect(publisherId).toBe(hashedUsername) }) @@ -55,7 +55,7 @@ describe('MessageCreationUtil', () => { username: 'username', }), } - const msgCreationUtil = new MessageCreationUtil(client.options.auth, undefined, client.getUserInfo) + const msgCreationUtil = new MessageCreationUtil(client) const publisherId = await msgCreationUtil.getPublisherId() expect(publisherId).toBe(hashedUsername) }) @@ -71,7 +71,7 @@ describe('MessageCreationUtil', () => { username: 'username', }), } - const msgCreationUtil = new MessageCreationUtil(client.options.auth, undefined, client.getUserInfo) + const msgCreationUtil = new MessageCreationUtil(client) const publisherId = await msgCreationUtil.getPublisherId() expect(publisherId).toBe(hashedUsername) }) @@ -144,7 +144,7 @@ describe('MessageCreationUtil', () => { }), getStream: sinon.stub().resolves(stream), } - msgCreationUtil = new MessageCreationUtil(client.options.auth, client.signer, client.getUserInfo(), client.getStream) + msgCreationUtil = new MessageCreationUtil(client) }) afterAll(() => { @@ -153,13 +153,11 @@ describe('MessageCreationUtil', () => { function getStreamMessage(streamId, timestamp, sequenceNumber, prevMsgRef) { return new StreamMessage({ - messageId: new MessageID(streamId, 0, timestamp, sequenceNumber, hashedUsername, msgCreationUtil.msgChainId), + messageId: new MessageID(streamId, 0, timestamp, sequenceNumber, hashedUsername, msgCreationUtil.msgChainer.msgChainId), prevMesssageRef: prevMsgRef, content: pubMsg, messageType: StreamMessage.MESSAGE_TYPES.MESSAGE, encryptionType: StreamMessage.ENCRYPTION_TYPES.NONE, - signatureType: StreamMessage.SIGNATURE_TYPES.ETH, - signature: 'signature', }) } @@ -171,7 +169,9 @@ describe('MessageCreationUtil', () => { /* eslint-disable no-loop-func */ prevMsgRef = new MessageRef(ts, i) promises.push(async () => { - const streamMessage = await msgCreationUtil.createStreamMessage(stream, pubMsg, ts) + const streamMessage = await msgCreationUtil.createStreamMessage(stream, { + data: pubMsg, timestamp: ts + }) expect(streamMessage).toStrictEqual(getStreamMessage('streamId', ts, i, prevMsgRef)) }) /* eslint-enable no-loop-func */ @@ -187,7 +187,9 @@ describe('MessageCreationUtil', () => { prevMsgRef = new MessageRef(ts + i, i) /* eslint-disable no-loop-func */ promises.push(async () => { - const streamMessage = await msgCreationUtil.createStreamMessage(stream, pubMsg, ts + i) + const streamMessage = await msgCreationUtil.createStreamMessage(stream, { + data: pubMsg, timestamp: ts + i + }) expect(streamMessage).toStrictEqual(getStreamMessage('streamId', ts + i, 0, prevMsgRef)) }) /* eslint-enable no-loop-func */ @@ -206,23 +208,33 @@ describe('MessageCreationUtil', () => { partitions: 1, }) - const msg1 = await msgCreationUtil.createStreamMessage(stream, pubMsg, ts) - const msg2 = await msgCreationUtil.createStreamMessage(stream2, pubMsg, ts) - const msg3 = await msgCreationUtil.createStreamMessage(stream3, pubMsg, ts) + const msg1 = await msgCreationUtil.createStreamMessage(stream, { + data: pubMsg, timestamp: ts + }) + const msg2 = await msgCreationUtil.createStreamMessage(stream2, { + data: pubMsg, timestamp: ts + }) + const msg3 = await msgCreationUtil.createStreamMessage(stream3, { + data: pubMsg, timestamp: ts + }) expect(msg1).toEqual(getStreamMessage('streamId', ts, 0, null)) expect(msg2).toEqual(getStreamMessage('streamId2', ts, 0, null)) expect(msg3).toEqual(getStreamMessage('streamId3', ts, 0, null)) }) - it('should sign messages if signer is defined', async () => { - const msg1 = await msgCreationUtil.createStreamMessage(stream, pubMsg, Date.now()) + it.skip('should sign messages if signer is defined', async () => { + const msg1 = await msgCreationUtil.createStreamMessage(stream, { + data: pubMsg, timestamp: Date.now() + }) expect(msg1.signature).toBe('signature') }) it('should create message from a stream id by fetching the stream', async () => { const ts = Date.now() - const streamMessage = await msgCreationUtil.createStreamMessage(stream.id, pubMsg, ts) + const streamMessage = await msgCreationUtil.createStreamMessage(stream.id, { + data: pubMsg, timestamp: ts + }) expect(streamMessage).toEqual(getStreamMessage(stream.id, ts, 0, null)) }) }) diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index 53825e652..24014b52a 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -1233,7 +1233,7 @@ describe('StreamrClient', () => { describe('publish', () => { function getPublishRequest(content, streamId, timestamp, seqNum, prevMsgRef, requestId) { const { hashedUsername } = StubbedStreamrClient - const { msgChainId } = client.publisher.msgCreationUtil + const { msgChainId } = client.publisher.msgCreationUtil.msgChainer const messageId = new MessageID(streamId, 0, timestamp, seqNum, hashedUsername, msgChainId) const streamMessage = new StreamMessage({ messageId, From 84d511f73da6b3cbe70c92ec8a6348f2dd45c862 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 16 Sep 2020 14:56:37 -0400 Subject: [PATCH 055/517] Avoid using expensive ethers.Wallet.createRandom() calls in test. e.g. 1000x calls with ethers: 14s, randomBytes: 3.5ms. --- test/integration/MultipleClients.test.js | 26 ++--- test/integration/ResendReconnect.test.js | 8 +- test/integration/Session.test.js | 5 +- test/integration/StreamEndpoints.test.js | 2 +- test/integration/StreamrClient.test.js | 5 +- test/integration/Subscription.test.js | 5 +- test/integration/authFetch.test.js | 4 +- test/unit/MessageCreationUtil.test.js | 126 +++++++++++------------ test/unit/StreamrClient.test.js | 1 - test/utils.js | 6 ++ 10 files changed, 87 insertions(+), 101 deletions(-) diff --git a/test/integration/MultipleClients.test.js b/test/integration/MultipleClients.test.js index 8dbf29912..7ce858c25 100644 --- a/test/integration/MultipleClients.test.js +++ b/test/integration/MultipleClients.test.js @@ -1,14 +1,14 @@ -import { ethers } from 'ethers' import { wait } from 'streamr-test-utils' -import { uid } from '../utils' +import { uid, fakePrivateKey } from '../utils' import StreamrClient from '../../src' +import Connection from '../../src/Connection' import config from './config' const createClient = (opts = {}) => new StreamrClient({ auth: { - privateKey: ethers.Wallet.createRandom().privateKey, + privateKey: fakePrivateKey() }, autoConnect: false, autoDisconnect: false, @@ -24,8 +24,8 @@ describe('PubSub with multiple clients', () => { let otherClient let privateKey - async function setup() { - privateKey = ethers.Wallet.createRandom().privateKey + beforeEach(async () => { + privateKey = fakePrivateKey() mainClient = createClient({ auth: { @@ -36,12 +36,11 @@ describe('PubSub with multiple clients', () => { stream = await mainClient.createStream({ name: uid('stream') }) - } + }) - async function teardown() { + afterEach(async () => { if (stream) { await stream.delete() - stream = undefined // eslint-disable-line require-atomic-updates } if (mainClient) { @@ -51,14 +50,11 @@ describe('PubSub with multiple clients', () => { if (otherClient) { await otherClient.ensureDisconnected() } - } - - beforeEach(async () => { - await setup() - }) - afterEach(async () => { - await teardown() + const openSockets = Connection.getOpen() + if (openSockets !== 0) { + throw new Error(`sockets not closed: ${openSockets}`) + } }) test('can get messages published from other client', async (done) => { diff --git a/test/integration/ResendReconnect.test.js b/test/integration/ResendReconnect.test.js index c324dac11..c79cd9e86 100644 --- a/test/integration/ResendReconnect.test.js +++ b/test/integration/ResendReconnect.test.js @@ -1,15 +1,13 @@ -import { ethers } from 'ethers' +import { wait } from 'streamr-test-utils' -import { uid } from '../utils' +import { uid, fakePrivateKey } from '../utils' import StreamrClient from '../../src' import config from './config' -const { wait } = require('streamr-test-utils') - const createClient = (opts = {}) => new StreamrClient({ auth: { - privateKey: ethers.Wallet.createRandom().privateKey, + privateKey: fakePrivateKey(), }, autoConnect: false, autoDisconnect: false, diff --git a/test/integration/Session.test.js b/test/integration/Session.test.js index 60d24cdef..a6c638a3f 100644 --- a/test/integration/Session.test.js +++ b/test/integration/Session.test.js @@ -1,6 +1,5 @@ -import { ethers } from 'ethers' - import StreamrClient from '../../src' +import { fakePrivateKey } from '../utils' import config from './config' @@ -37,7 +36,7 @@ describe('Session', () => { expect.assertions(1) await expect(createClient({ auth: { - privateKey: ethers.Wallet.createRandom().privateKey, + privateKey: fakePrivateKey(), }, }).session.getSessionToken()).resolves.toBeTruthy() }) diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.js index dc5bae1b5..2aa62b7de 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.js @@ -167,7 +167,7 @@ describe('StreamEndpoints', () => { }) describe('Stream configuration', () => { - it.skip('Stream.detectFields', async () => { + it('Stream.detectFields', async () => { await client.ensureConnected() await client.publish(createdStream.id, { foo: 'bar', diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index 5d8873bad..e71eb6993 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -4,9 +4,8 @@ import path from 'path' import fetch from 'node-fetch' import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import { wait, waitForEvent } from 'streamr-test-utils' -import { ethers } from 'ethers' -import { uid } from '../utils' +import { uid, fakePrivateKey } from '../utils' import StreamrClient from '../../src' import Connection from '../../src/Connection' @@ -25,7 +24,7 @@ describe('StreamrClient', () => { const createClient = (opts = {}) => { const c = new StreamrClient({ auth: { - privateKey: ethers.Wallet.createRandom().privateKey, + privateKey: fakePrivateKey(), }, autoConnect: false, autoDisconnect: false, diff --git a/test/integration/Subscription.test.js b/test/integration/Subscription.test.js index 17c7d6a5b..7f9284fb6 100644 --- a/test/integration/Subscription.test.js +++ b/test/integration/Subscription.test.js @@ -1,14 +1,13 @@ -import { ethers } from 'ethers' import { wait, waitForEvent } from 'streamr-test-utils' -import { uid } from '../utils' +import { uid, fakePrivateKey } from '../utils' import StreamrClient from '../../src' import config from './config' const createClient = (opts = {}) => new StreamrClient({ auth: { - privateKey: ethers.Wallet.createRandom().privateKey, + privateKey: fakePrivateKey(), }, autoConnect: false, autoDisconnect: false, diff --git a/test/integration/authFetch.test.js b/test/integration/authFetch.test.js index 12caec830..1c8749049 100644 --- a/test/integration/authFetch.test.js +++ b/test/integration/authFetch.test.js @@ -1,9 +1,9 @@ jest.mock('node-fetch') -import { ethers } from 'ethers' import fetch from 'node-fetch' import StreamrClient from '../../src' +import { fakePrivateKey } from '../utils' import config from './config' @@ -27,7 +27,7 @@ describe('authFetch', () => { fetch.mockImplementation(realFetch) client = new StreamrClient({ auth: { - privateKey: ethers.Wallet.createRandom().privateKey, + privateKey: fakePrivateKey() }, autoConnect: false, autoDisconnect: false, diff --git a/test/unit/MessageCreationUtil.test.js b/test/unit/MessageCreationUtil.test.js index 82eb18f46..1e5252273 100644 --- a/test/unit/MessageCreationUtil.test.js +++ b/test/unit/MessageCreationUtil.test.js @@ -5,72 +5,67 @@ import { MessageLayer } from 'streamr-client-protocol' import { MessageCreationUtil } from '../../src/Publisher' import Stream from '../../src/rest/domain/Stream' +// eslint-disable-next-line import/no-named-as-default-member +import StubbedStreamrClient from './StubbedStreamrClient' + const { StreamMessage, MessageID, MessageRef } = MessageLayer describe('MessageCreationUtil', () => { const hashedUsername = '0x16F78A7D6317F102BBD95FC9A4F3FF2E3249287690B8BDAD6B7810F82B34ACE3'.toLowerCase() + const createClient = (opts = {}) => { + return new StubbedStreamrClient({ + auth: { + username: 'username', + }, + autoConnect: false, + autoDisconnect: false, + maxRetries: 2, + ...opts, + }) + } + describe('getPublisherId', () => { - it('uses address', async () => { + it('uses address for privateKey auth', async () => { const wallet = ethers.Wallet.createRandom() - const client = { - options: { - auth: { - privateKey: wallet.privateKey, - }, + const client = createClient({ + auth: { + privateKey: wallet.privateKey, }, - getUserInfo: sinon.stub().resolves({ - username: 'username', - }), - } + }) const msgCreationUtil = new MessageCreationUtil(client) const publisherId = await msgCreationUtil.getPublisherId() expect(publisherId).toBe(wallet.address.toLowerCase()) }) - it('uses hash of username', async () => { - const client = { - options: { - auth: { - apiKey: 'apiKey', - }, + it('uses hash of username for apiKey auth', async () => { + const client = createClient({ + auth: { + apiKey: 'apiKey', }, - getUserInfo: sinon.stub().resolves({ - username: 'username', - }), - } + }) const msgCreationUtil = new MessageCreationUtil(client) const publisherId = await msgCreationUtil.getPublisherId() expect(publisherId).toBe(hashedUsername) }) - it('uses hash of username', async () => { - const client = { - options: { - auth: { - username: 'username', - }, - }, - getUserInfo: sinon.stub().resolves({ + it('uses hash of username for username auth', async () => { + const client = createClient({ + auth: { username: 'username', - }), - } + }, + }) const msgCreationUtil = new MessageCreationUtil(client) const publisherId = await msgCreationUtil.getPublisherId() expect(publisherId).toBe(hashedUsername) }) - it('uses hash of username', async () => { - const client = { - options: { - auth: { - sessionToken: 'session-token', - }, + it('uses hash of username for sessionToken auth', async () => { + const client = createClient({ + auth: { + sessionToken: 'session-token', }, - getUserInfo: sinon.stub().resolves({ - username: 'username', - }), - } + }) const msgCreationUtil = new MessageCreationUtil(client) const publisherId = await msgCreationUtil.getPublisherId() expect(publisherId).toBe(hashedUsername) @@ -78,15 +73,21 @@ describe('MessageCreationUtil', () => { }) describe('partitioner', () => { + let client + + beforeAll(() => { + client = createClient() + }) + it('should throw if partition count is not defined', () => { expect(() => { - new MessageCreationUtil().computeStreamPartition(undefined, 'foo') + new MessageCreationUtil(client).computeStreamPartition(undefined, 'foo') }).toThrow() }) it('should always return partition 0 for all keys if partition count is 1', () => { for (let i = 0; i < 100; i++) { - expect(new MessageCreationUtil().computeStreamPartition(1, `foo${i}`)).toEqual(0) + expect(new MessageCreationUtil(client).computeStreamPartition(1, `foo${i}`)).toEqual(0) } }) @@ -104,7 +105,7 @@ describe('MessageCreationUtil', () => { expect(correctResults.length).toEqual(keys.length) for (let i = 0; i < keys.length; i++) { - const partition = new MessageCreationUtil().computeStreamPartition(10, keys[i]) + const partition = new MessageCreationUtil(client).computeStreamPartition(10, keys[i]) expect(correctResults[i]).toStrictEqual(partition) } }) @@ -115,35 +116,24 @@ describe('MessageCreationUtil', () => { foo: 'bar', } - const stream = new Stream(null, { - id: 'streamId', - partitions: 1, - }) - let client let msgCreationUtil + let stream - beforeEach(() => { - client = { - options: { - auth: { - username: 'username', - }, - }, - signer: { - signStreamMessage: (streamMessage) => { - /* eslint-disable no-param-reassign */ - streamMessage.signatureType = StreamMessage.SIGNATURE_TYPES.ETH - streamMessage.signature = 'signature' - /* eslint-enable no-param-reassign */ - return Promise.resolve() - }, - }, - getUserInfo: () => Promise.resolve({ + beforeAll(() => { + client = createClient({ + auth: { username: 'username', - }), - getStream: sinon.stub().resolves(stream), - } + }, + }) + }) + + beforeEach(() => { + stream = new Stream(null, { + id: 'streamId', + partitions: 1, + }) + client.getStream = sinon.stub().resolves(stream) msgCreationUtil = new MessageCreationUtil(client) }) diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index 24014b52a..492d6bbec 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -6,7 +6,6 @@ import { wait, waitForEvent } from 'streamr-test-utils' import FailedToPublishError from '../../src/errors/FailedToPublishError' import Subscription from '../../src/Subscription' import Connection from '../../src/Connection' -// import StreamrClient from '../../src/StreamrClient' import { uid } from '../utils' // eslint-disable-next-line import/no-named-as-default-member diff --git a/test/utils.js b/test/utils.js index 88ae9bee9..524b90c3d 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,3 +1,9 @@ +const crypto = require('crypto') + const uniqueId = require('lodash.uniqueid') export const uid = (prefix) => uniqueId(`p${process.pid}${prefix ? '-' + prefix : ''}`) + +export function fakePrivateKey() { + return crypto.randomBytes(32).toString('hex') +} From 4ccead951a4298dfd67ef0be8a85bd2587a2c121 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 16 Sep 2020 14:57:32 -0400 Subject: [PATCH 056/517] Ensure messageCreationUtil is cleaned up after test. --- src/Publisher.js | 7 +++-- test/unit/MessageCreationUtil.test.js | 41 ++++++++++++++++----------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/Publisher.js b/src/Publisher.js index 3551736f1..92b734c6e 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -81,7 +81,6 @@ export class MessageCreationUtil { return partitions }) this.getUserInfo = AsyncCacheFn(this.getUserInfo.bind(this)) - this.getPublisherId = AsyncCacheFn(this.getPublisherId.bind(this)) this.cachedHashes = {} } @@ -247,10 +246,14 @@ export default class Publisher { } getPublisherId() { + if (this.client.session.isUnauthenticated()) { + throw new Error('Need to be authenticated to getPublisherId.') + } return this.msgCreationUtil.getPublisherId() } stop() { - return this.msgCreationUtil.stop() + if (!this.msgCreationUtil) { return } + this.msgCreationUtil.stop() } } diff --git a/test/unit/MessageCreationUtil.test.js b/test/unit/MessageCreationUtil.test.js index 1e5252273..0a5f9820b 100644 --- a/test/unit/MessageCreationUtil.test.js +++ b/test/unit/MessageCreationUtil.test.js @@ -11,6 +11,8 @@ import StubbedStreamrClient from './StubbedStreamrClient' const { StreamMessage, MessageID, MessageRef } = MessageLayer describe('MessageCreationUtil', () => { + let client + let msgCreationUtil const hashedUsername = '0x16F78A7D6317F102BBD95FC9A4F3FF2E3249287690B8BDAD6B7810F82B34ACE3'.toLowerCase() const createClient = (opts = {}) => { @@ -25,69 +27,78 @@ describe('MessageCreationUtil', () => { }) } + afterEach(async () => { + msgCreationUtil.stop() + if (client) { + await client.disconnect() + } + }) + describe('getPublisherId', () => { it('uses address for privateKey auth', async () => { const wallet = ethers.Wallet.createRandom() - const client = createClient({ + client = createClient({ auth: { privateKey: wallet.privateKey, }, }) - const msgCreationUtil = new MessageCreationUtil(client) + msgCreationUtil = new MessageCreationUtil(client) const publisherId = await msgCreationUtil.getPublisherId() expect(publisherId).toBe(wallet.address.toLowerCase()) }) it('uses hash of username for apiKey auth', async () => { - const client = createClient({ + client = createClient({ auth: { apiKey: 'apiKey', }, }) - const msgCreationUtil = new MessageCreationUtil(client) + msgCreationUtil = new MessageCreationUtil(client) const publisherId = await msgCreationUtil.getPublisherId() expect(publisherId).toBe(hashedUsername) }) it('uses hash of username for username auth', async () => { - const client = createClient({ + client = createClient({ auth: { username: 'username', }, }) - const msgCreationUtil = new MessageCreationUtil(client) + msgCreationUtil = new MessageCreationUtil(client) const publisherId = await msgCreationUtil.getPublisherId() expect(publisherId).toBe(hashedUsername) }) it('uses hash of username for sessionToken auth', async () => { - const client = createClient({ + client = createClient({ auth: { sessionToken: 'session-token', }, }) - const msgCreationUtil = new MessageCreationUtil(client) + msgCreationUtil = new MessageCreationUtil(client) const publisherId = await msgCreationUtil.getPublisherId() expect(publisherId).toBe(hashedUsername) }) }) describe('partitioner', () => { - let client - beforeAll(() => { client = createClient() }) + beforeEach(() => { + msgCreationUtil = new MessageCreationUtil(client) + }) + it('should throw if partition count is not defined', () => { expect(() => { - new MessageCreationUtil(client).computeStreamPartition(undefined, 'foo') + msgCreationUtil.computeStreamPartition(undefined, 'foo') }).toThrow() }) it('should always return partition 0 for all keys if partition count is 1', () => { for (let i = 0; i < 100; i++) { - expect(new MessageCreationUtil(client).computeStreamPartition(1, `foo${i}`)).toEqual(0) + expect(msgCreationUtil.computeStreamPartition(1, `foo${i}`)).toEqual(0) } }) @@ -105,7 +116,7 @@ describe('MessageCreationUtil', () => { expect(correctResults.length).toEqual(keys.length) for (let i = 0; i < keys.length; i++) { - const partition = new MessageCreationUtil(client).computeStreamPartition(10, keys[i]) + const partition = msgCreationUtil.computeStreamPartition(10, keys[i]) expect(correctResults[i]).toStrictEqual(partition) } }) @@ -116,8 +127,6 @@ describe('MessageCreationUtil', () => { foo: 'bar', } - let client - let msgCreationUtil let stream beforeAll(() => { @@ -137,7 +146,7 @@ describe('MessageCreationUtil', () => { msgCreationUtil = new MessageCreationUtil(client) }) - afterAll(() => { + afterEach(() => { msgCreationUtil.stop() }) From 8253cde1a1b281e5becc305fad6264d0e746ec80 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 16 Sep 2020 15:47:05 -0400 Subject: [PATCH 057/517] Fix non-functional MessageCreationUtil test. --- test/unit/MessageCreationUtil.test.js | 34 ++++++++++----------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/test/unit/MessageCreationUtil.test.js b/test/unit/MessageCreationUtil.test.js index 0a5f9820b..05ac05754 100644 --- a/test/unit/MessageCreationUtil.test.js +++ b/test/unit/MessageCreationUtil.test.js @@ -153,7 +153,7 @@ describe('MessageCreationUtil', () => { function getStreamMessage(streamId, timestamp, sequenceNumber, prevMsgRef) { return new StreamMessage({ messageId: new MessageID(streamId, 0, timestamp, sequenceNumber, hashedUsername, msgCreationUtil.msgChainer.msgChainId), - prevMesssageRef: prevMsgRef, + prevMsgRef, content: pubMsg, messageType: StreamMessage.MESSAGE_TYPES.MESSAGE, encryptionType: StreamMessage.ENCRYPTION_TYPES.NONE, @@ -162,38 +162,28 @@ describe('MessageCreationUtil', () => { it('should create messages with increasing sequence numbers', async () => { const ts = Date.now() - const promises = [] let prevMsgRef = null for (let i = 0; i < 10; i++) { - /* eslint-disable no-loop-func */ - prevMsgRef = new MessageRef(ts, i) - promises.push(async () => { - const streamMessage = await msgCreationUtil.createStreamMessage(stream, { - data: pubMsg, timestamp: ts - }) - expect(streamMessage).toStrictEqual(getStreamMessage('streamId', ts, i, prevMsgRef)) + // eslint-disable-next-line no-await-in-loop + const streamMessage = await msgCreationUtil.createStreamMessage(stream, { + data: pubMsg, timestamp: ts, }) - /* eslint-enable no-loop-func */ + expect(streamMessage).toStrictEqual(getStreamMessage('streamId', ts, i, prevMsgRef)) + prevMsgRef = new MessageRef(ts, i) } - await Promise.all(promises) }) - it('should create messages with sequence number 0', async () => { + it('should create messages with sequence number 0 if different timestamp', async () => { const ts = Date.now() - const promises = [] let prevMsgRef = null for (let i = 0; i < 10; i++) { - prevMsgRef = new MessageRef(ts + i, i) - /* eslint-disable no-loop-func */ - promises.push(async () => { - const streamMessage = await msgCreationUtil.createStreamMessage(stream, { - data: pubMsg, timestamp: ts + i - }) - expect(streamMessage).toStrictEqual(getStreamMessage('streamId', ts + i, 0, prevMsgRef)) + // eslint-disable-next-line no-await-in-loop + const streamMessage = await msgCreationUtil.createStreamMessage(stream, { + data: pubMsg, timestamp: ts + i, }) - /* eslint-enable no-loop-func */ + expect(streamMessage).toStrictEqual(getStreamMessage('streamId', ts + i, 0, prevMsgRef)) + prevMsgRef = new MessageRef(ts + i, 0) } - await Promise.all(promises) }) it('should publish messages with sequence number 0 (different streams)', async () => { From 0e554e36c7cebd2dba9d2bae08044fa9d97f73a8 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 16 Sep 2020 15:57:00 -0400 Subject: [PATCH 058/517] Swap out receptacle for more flexible mem/p-memoize/quick-lru. --- package-lock.json | 58 ++++++++++++++--- package.json | 4 +- src/Publisher.js | 94 ++++++++++++--------------- src/utils.js | 74 +++++++++------------ test/unit/MessageCreationUtil.test.js | 9 ++- 5 files changed, 129 insertions(+), 110 deletions(-) diff --git a/package-lock.json b/package-lock.json index 23e7fc301..833bdfcb4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9274,6 +9274,14 @@ "tmpl": "1.0.x" } }, + "map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "requires": { + "p-defer": "^1.0.0" + } + }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -9306,6 +9314,22 @@ "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, + "mem": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/mem/-/mem-6.1.1.tgz", + "integrity": "sha512-Ci6bIfq/UgcxPTYa8dQQ5FY3BzKkT894bwXWXxC/zqs0XgMO2cT20CGkOqda7gZNkmK5VP4x89IGZ6K7hfbn3Q==", + "requires": { + "map-age-cleaner": "^0.1.3", + "mimic-fn": "^3.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==" + } + } + }, "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", @@ -10437,6 +10461,11 @@ "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", "dev": true }, + "p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + }, "p-each-series": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", @@ -10476,6 +10505,22 @@ "aggregate-error": "^3.0.0" } }, + "p-memoize": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-memoize/-/p-memoize-4.0.0.tgz", + "integrity": "sha512-oMxCJKVS75Bf2RWtXJNQNaX2K1G0FYpllOh2iTsPXZqnf9dWMcis3BL+pRdLeQY8lIdwwL01k/UV5LBdcVhZzg==", + "requires": { + "mem": "^6.0.1", + "mimic-fn": "^3.0.0" + }, + "dependencies": { + "mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==" + } + } + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -11015,6 +11060,11 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, + "quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -11192,14 +11242,6 @@ "readable-stream": "^2.0.2" } }, - "receptacle": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/receptacle/-/receptacle-1.3.2.tgz", - "integrity": "sha512-HrsFvqZZheusncQRiEE7GatOAETrARKV/lnfYicIm8lbvp/JQOdADOfhjBd2DajvoszEyxSM6RlAAIZgEoeu/A==", - "requires": { - "ms": "^2.1.1" - } - }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", diff --git a/package.json b/package.json index 767db4f1b..7c4c5d609 100644 --- a/package.json +++ b/package.json @@ -71,13 +71,15 @@ "ethers": "^5.0.12", "eventemitter3": "^4.0.7", "lodash.uniqueid": "^4.0.1", + "mem": "^6.1.1", "node-fetch": "^2.6.1", "node-webcrypto-ossl": "^2.1.1", "once": "^1.4.0", + "p-memoize": "^4.0.0", "promise-memoize": "^1.2.1", "qs": "^6.9.4", + "quick-lru": "^5.1.1", "randomstring": "^1.1.5", - "receptacle": "^1.3.2", "streamr-client-protocol": "^5.3.3", "uuid": "^8.3.0", "webpack-node-externals": "^2.5.2", diff --git a/src/Publisher.js b/src/Publisher.js index 92b734c6e..939c94c58 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -7,7 +7,7 @@ import { ethers } from 'ethers' import Signer from './Signer' import Stream from './rest/domain/Stream' import FailedToPublishError from './errors/FailedToPublishError' -import { AsyncCacheMap, AsyncCacheFn } from './utils' +import { CacheAsyncFn, CacheFn } from './utils' const { StreamMessage, MessageID, MessageRef } = MessageLayer @@ -23,77 +23,62 @@ function getStreamId(streamObjectOrId) { throw new Error(`First argument must be a Stream object or the stream id! Was: ${streamObjectOrId}`) } -class MessageChainer { +function hash(stringToHash) { + return crypto.createHash('md5').update(stringToHash).digest() +} + +class OrderedMessageChainCreator { constructor() { this.msgChainId = randomstring.generate(20) - this.publishedStreams = {} + this.messageRefs = new Map() } - create(streamId, streamPartition, timestamp, publisherId) { - const key = streamId + streamPartition - if (!this.publishedStreams[key]) { - this.publishedStreams[key] = { - prevTimestamp: null, - prevSequenceNumber: 0, - } - } - + create({ streamId, streamPartition, timestamp, publisherId, }) { + const key = `${streamId}|${streamPartition}` + const prevMsgRef = this.messageRefs.get(key) const sequenceNumber = this.getNextSequenceNumber(key, timestamp) const messageId = new MessageID(streamId, streamPartition, timestamp, sequenceNumber, publisherId, this.msgChainId) - const prevMsgRef = this.getPrevMsgRef(key) - this.publishedStreams[key].prevTimestamp = timestamp - this.publishedStreams[key].prevSequenceNumber = sequenceNumber + this.messageRefs.set(key, new MessageRef(timestamp, sequenceNumber)) return [messageId, prevMsgRef] } - getPrevMsgRef(key) { - const prevTimestamp = this.getPrevTimestamp(key) - if (!prevTimestamp) { - return null - } - const prevSequenceNumber = this.getPrevSequenceNumber(key) - return new MessageRef(prevTimestamp, prevSequenceNumber) - } - getNextSequenceNumber(key, timestamp) { - if (timestamp !== this.getPrevTimestamp(key)) { + if (!this.messageRefs.has(key)) { return 0 } + const prev = this.messageRefs.get(key) + if (timestamp !== prev.timestamp) { return 0 } - return this.getPrevSequenceNumber(key) + 1 + return prev.sequenceNumber + 1 } - getPrevTimestamp(key) { - return this.publishedStreams[key] && this.publishedStreams[key].prevTimestamp - } - - getPrevSequenceNumber(key) { - return this.publishedStreams[key].prevSequenceNumber + clear() { + this.messageRefs.clear() } } export class MessageCreationUtil { constructor(client) { this.client = client - this.msgChainer = new MessageChainer() + const cacheOptions = client.options.cache + this.msgChainer = new OrderedMessageChainCreator(cacheOptions) - this.streamPartitionCache = new AsyncCacheMap(async (streamId) => { - const { partitions } = await this.client.getStream(streamId) - return partitions - }) - this.getUserInfo = AsyncCacheFn(this.getUserInfo.bind(this)) - this.getPublisherId = AsyncCacheFn(this.getPublisherId.bind(this)) - this.cachedHashes = {} + this._getStreamPartitions = CacheAsyncFn(this._getStreamPartitions.bind(this), cacheOptions) + this.getUserInfo = CacheAsyncFn(this.getUserInfo.bind(this), cacheOptions) + this.getPublisherId = CacheAsyncFn(this.getPublisherId.bind(this), cacheOptions) + this.hash = CacheFn(hash, cacheOptions) } stop() { - this.msgChainer = new MessageChainer() - this.getUserInfo.stop() - this.getPublisherId.stop() - this.streamPartitionCache.stop() + this.msgChainer.clear() + this.msgChainer = new OrderedMessageChainCreator() + this.getUserInfo.clear() + this.getPublisherId.clear() + this._getStreamPartitions.clear() + this.hash.clear() } async getPublisherId() { - const { auth = {} } = this.client.options + const { options: { auth = {} } = {} } = this.client if (auth.privateKey !== undefined) { return ethers.utils.computeAddress(auth.privateKey).toLowerCase() } @@ -129,15 +114,15 @@ export class MessageCreationUtil { if (streamObjectOrId && streamObjectOrId.partitions != null) { return streamObjectOrId.partitions } + + // get streamId here so caching based on id works const streamId = getStreamId(streamObjectOrId) - return this.streamPartitionCache.load(streamId) + return this._getStreamPartitions(streamId) } - hash(stringToHash) { - if (this.cachedHashes[stringToHash] === undefined) { - this.cachedHashes[stringToHash] = crypto.createHash('md5').update(stringToHash).digest() - } - return this.cachedHashes[stringToHash] + async _getStreamPartitions(streamId) { + const { partitions } = await this.client.getStream(streamId) + return partitions } computeStreamPartition(partitionCount, partitionKey) { @@ -169,7 +154,12 @@ export class MessageCreationUtil { ]) const streamPartition = this.computeStreamPartition(streamPartitions, partitionKey) - const [messageId, prevMsgRef] = this.msgChainer.create(streamId, streamPartition, timestamp, publisherId) + const [messageId, prevMsgRef] = this.msgChainer.create({ + streamId, + streamPartition, + timestamp, + publisherId, + }) return new StreamMessage({ messageId, diff --git a/src/utils.js b/src/utils.js index 75a34e0b9..62410cfb0 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,6 +1,8 @@ -import Receptacle from 'receptacle' import { v4 as uuidv4 } from 'uuid' import uniqueId from 'lodash.uniqueid' +import LRU from 'quick-lru' +import pMemoize from 'p-memoize' +import mem from 'mem' import pkg from '../package.json' @@ -41,50 +43,34 @@ export const getEndpointUrl = (baseUrl, ...pathParts) => { return baseUrl + '/' + pathParts.map((part) => encodeURIComponent(part)).join('/') } -export class AsyncCacheMap { - /* eslint-disable object-curly-newline */ - constructor(fn, { - max = 10000, - ttl = 30 * 60 * 1000, // 30 minutes - refresh = true, // reset ttl on access - } = {}) { - /* eslint-disable-next-line object-curly-newline */ - this.ttl = ttl - this.refresh = refresh - this.fn = fn - this.cache = new Receptacle({ - max, +/* eslint-disable object-curly-newline */ +export function CacheAsyncFn(fn, { + maxSize = 10000, + maxAge = 30 * 60 * 1000, // 30 minutes + cachePromiseRejection = false, +} = {}) { + const cachedFn = pMemoize(fn, { + maxAge, + cachePromiseRejection, + cache: new LRU({ + maxSize, }) - } - - load(id, { ttl = this.ttl, refresh = this.refresh, } = {}) { - if (!this.cache.get(id)) { - const promise = this.fn(id) - const success = this.cache.set(id, promise, { - ttl, - refresh, - }) - if (!success) { - console.warn(`Could not store ${id} in local cache.`) - return promise - } - } - return this.cache.get(id) - } - - stop() { - this.cache.clear() - } + }) + cachedFn.clear = () => pMemoize.clear(cachedFn) + return cachedFn } -export function AsyncCacheFn(fn, options) { - const cache = new AsyncCacheMap(fn, options) - const cacheFn = async (opts) => { - return cache.load('value', opts) - } - cacheFn.cache = cache - cacheFn.stop = () => { - return cache.stop() - } - return cacheFn +export function CacheFn(fn, { + maxSize = 10000, + maxAge = 30 * 60 * 1000, // 30 minutes +} = {}) { + const cachedFn = mem(fn, { + maxAge, + cache: new LRU({ + maxSize, + }) + }) + cachedFn.clear = () => mem.clear(cachedFn) + return cachedFn } +/* eslint-enable object-curly-newline */ diff --git a/test/unit/MessageCreationUtil.test.js b/test/unit/MessageCreationUtil.test.js index 05ac05754..e3fb43f5c 100644 --- a/test/unit/MessageCreationUtil.test.js +++ b/test/unit/MessageCreationUtil.test.js @@ -28,7 +28,10 @@ describe('MessageCreationUtil', () => { } afterEach(async () => { - msgCreationUtil.stop() + if (msgCreationUtil) { + msgCreationUtil.stop() + } + if (client) { await client.disconnect() } @@ -146,10 +149,6 @@ describe('MessageCreationUtil', () => { msgCreationUtil = new MessageCreationUtil(client) }) - afterEach(() => { - msgCreationUtil.stop() - }) - function getStreamMessage(streamId, timestamp, sequenceNumber, prevMsgRef) { return new StreamMessage({ messageId: new MessageID(streamId, 0, timestamp, sequenceNumber, hashedUsername, msgCreationUtil.msgChainer.msgChainId), From a2af7529ac38ded85cb9081e4542828c67ffb706 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 16 Sep 2020 16:08:39 -0400 Subject: [PATCH 059/517] Convert LoginEndpoints test to async/await. --- test/integration/LoginEndpoints.test.js | 95 +++++++++++-------------- 1 file changed, 40 insertions(+), 55 deletions(-) diff --git a/test/integration/LoginEndpoints.test.js b/test/integration/LoginEndpoints.test.js index 13d17f783..0d3190c0f 100644 --- a/test/integration/LoginEndpoints.test.js +++ b/test/integration/LoginEndpoints.test.js @@ -22,79 +22,63 @@ describe('LoginEndpoints', () => { }) afterAll(async (done) => { - await client.ensureDisconnected() + await client.disconnect() done() }) describe('Challenge generation', () => { - it('should retrieve a challenge', () => client.getChallenge('some-address') - .then((challenge) => { - assert(challenge) - assert(challenge.id) - assert(challenge.challenge) - assert(challenge.expires) - })) + it('should retrieve a challenge', async () => { + const challenge = await client.getChallenge('some-address') + assert(challenge) + assert(challenge.id) + assert(challenge.challenge) + assert(challenge.expires) + }) }) - async function assertThrowsAsync(fn, regExp) { - let f = () => {} - try { - await fn() - } catch (e) { - f = () => { - throw e - } - } finally { - assert.throws(f, regExp) - } - } - describe('Challenge response', () => { it('should fail to get a session token', async () => { - await assertThrowsAsync(async () => client.sendChallengeResponse( - { + await expect(async () => { + await client.sendChallengeResponse({ id: 'some-id', challenge: 'some-challenge', - }, - 'some-sig', - 'some-address', - ), /Error/) + }, 'some-sig', 'some-address') + }).rejects.toThrow() }) - it('should get a session token', () => { + + it('should get a session token', async () => { const wallet = ethers.Wallet.createRandom() - return client.getChallenge(wallet.address) - .then(async (challenge) => { - assert(challenge.challenge) - const signature = await wallet.signMessage(challenge.challenge) - return client.sendChallengeResponse(challenge, signature, wallet.address) - .then((sessionToken) => { - assert(sessionToken) - assert(sessionToken.token) - assert(sessionToken.expires) - }) - }) + const challenge = await client.getChallenge(wallet.address) + assert(challenge.challenge) + const signature = await wallet.signMessage(challenge.challenge) + const sessionToken = await client.sendChallengeResponse(challenge, signature, wallet.address) + assert(sessionToken) + assert(sessionToken.token) + assert(sessionToken.expires) }) - it('should get a session token with combined function', () => { + + it('should get a session token with combined function', async () => { const wallet = ethers.Wallet.createRandom() - return client.loginWithChallengeResponse((d) => wallet.signMessage(d), wallet.address) - .then((sessionToken) => { - assert(sessionToken) - assert(sessionToken.token) - assert(sessionToken.expires) - }) + const sessionToken = await client.loginWithChallengeResponse((d) => wallet.signMessage(d), wallet.address) + assert(sessionToken) + assert(sessionToken.token) + assert(sessionToken.expires) }) }) describe('API key login', () => { it('should fail to get a session token', async () => { - await assertThrowsAsync(async () => client.loginWithApiKey('apikey'), /Error/) + await expect(async () => { + await client.loginWithApiKey('apikey') + }).rejects.toThrow() + }) + + it('should get a session token', async () => { + const sessionToken = await client.loginWithApiKey('tester1-api-key') + assert(sessionToken) + assert(sessionToken.token) + assert(sessionToken.expires) }) - it('should get a session token', () => client.loginWithApiKey('tester1-api-key') - .then((sessionToken) => { - assert(sessionToken) - assert(sessionToken.token) - assert(sessionToken.expires) - })) }) describe('Username/password login', () => { @@ -106,10 +90,11 @@ describe('LoginEndpoints', () => { }) describe('UserInfo', () => { - it('should get user info', () => client.getUserInfo().then((userInfo) => { + it('should get user info', async () => { + const userInfo = await client.getUserInfo() assert(userInfo.name) assert(userInfo.username) - })) + }) }) describe('logout', () => { From 7ae01601c106b9944b8b8f126be7b0c563d4f158 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 16 Sep 2020 16:09:13 -0400 Subject: [PATCH 060/517] Remove calls to ensureConnected/ensureDisconnected in test. --- test/benchmarks/publish.js | 4 ++-- test/flakey/DataUnionEndpoints.test.js | 10 +++++----- test/integration/MultipleClients.test.js | 8 ++++---- test/integration/ResendReconnect.test.js | 4 ++-- test/integration/Resends.test.js | 6 +++--- test/integration/StreamEndpoints.test.js | 4 ++-- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/test/benchmarks/publish.js b/test/benchmarks/publish.js index 3da517663..4a00e9ebc 100644 --- a/test/benchmarks/publish.js +++ b/test/benchmarks/publish.js @@ -63,8 +63,8 @@ async function run() { // eslint-disable-next-line no-console console.log('Disconnecting clients') await Promise.all([ - client1.ensureDisconnected(), - client2.ensureDisconnected(), + client1.disconnect(), + client2.disconnect(), ]) }) diff --git a/test/flakey/DataUnionEndpoints.test.js b/test/flakey/DataUnionEndpoints.test.js index 7682eaa14..b9e21fcd2 100644 --- a/test/flakey/DataUnionEndpoints.test.js +++ b/test/flakey/DataUnionEndpoints.test.js @@ -58,7 +58,7 @@ describe('DataUnionEndPoints', () => { }, 10000) beforeEach(async () => { - await adminClient.ensureConnected() + await adminClient.connect() dataUnion = await adminClient.deployDataUnion({ provider: testProvider, }) @@ -72,7 +72,7 @@ describe('DataUnionEndPoints', () => { afterAll(async () => { if (!adminClient) { return } - await adminClient.ensureDisconnected() + await adminClient.disconnect() }) afterAll(async () => { @@ -121,12 +121,12 @@ describe('DataUnionEndPoints', () => { autoDisconnect: false, ...config.clientOptions, }) - await memberClient.ensureConnected() + await memberClient.connect() }) afterAll(async () => { if (!memberClient) { return } - await memberClient.ensureDisconnected() + await memberClient.disconnect() }) it('can join the dataUnion, and get their balances and stats, and check proof, and withdraw', async () => { @@ -222,7 +222,7 @@ describe('DataUnionEndPoints', () => { }) afterAll(async () => { if (!client) { return } - await client.ensureDisconnected() + await client.disconnect() }) it('can get dataUnion stats, member list, and member stats', async () => { diff --git a/test/integration/MultipleClients.test.js b/test/integration/MultipleClients.test.js index 7ce858c25..05b6c294f 100644 --- a/test/integration/MultipleClients.test.js +++ b/test/integration/MultipleClients.test.js @@ -44,11 +44,11 @@ describe('PubSub with multiple clients', () => { } if (mainClient) { - await mainClient.ensureDisconnected() + await mainClient.disconnect() } if (otherClient) { - await otherClient.ensureDisconnected() + await otherClient.disconnect() } const openSockets = Connection.getOpen() @@ -65,8 +65,8 @@ describe('PubSub with multiple clients', () => { }) otherClient.once('error', done) mainClient.once('error', done) - await otherClient.ensureConnected() - await mainClient.ensureConnected() + await otherClient.connect() + await mainClient.connect() const receivedMessagesOther = [] const receivedMessagesMain = [] diff --git a/test/integration/ResendReconnect.test.js b/test/integration/ResendReconnect.test.js index c79cd9e86..b1a524a9d 100644 --- a/test/integration/ResendReconnect.test.js +++ b/test/integration/ResendReconnect.test.js @@ -27,7 +27,7 @@ describe('resend/reconnect', () => { beforeEach(async () => { client = createClient() - await client.ensureConnected() + await client.connect() publishedMessages = [] @@ -49,7 +49,7 @@ describe('resend/reconnect', () => { }, 10 * 1000) afterEach(async () => { - await client.ensureDisconnected() + await client.disconnect() }) describe('reconnect after resend', () => { diff --git a/test/integration/Resends.test.js b/test/integration/Resends.test.js index e980fc0f0..d7f2d2cc9 100644 --- a/test/integration/Resends.test.js +++ b/test/integration/Resends.test.js @@ -47,7 +47,7 @@ describe('StreamrClient resends', () => { }, 10 * 1000) afterEach(async () => { - await client.ensureDisconnected() + await client.disconnect() }) describe('issue resend and subscribe at the same time', () => { @@ -313,10 +313,10 @@ describe('StreamrClient resends', () => { } await wait(30000) - await client.ensureDisconnected() + await client.disconnect() // resend from LONG_RESEND messages - await client.ensureConnected() + await client.connect() const receivedMessages = [] const sub = await client.resend({ diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.js index 2aa62b7de..6a2fc5124 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.js @@ -168,7 +168,7 @@ describe('StreamEndpoints', () => { describe('Stream configuration', () => { it('Stream.detectFields', async () => { - await client.ensureConnected() + await client.connect() await client.publish(createdStream.id, { foo: 'bar', count: 0, @@ -189,7 +189,7 @@ describe('StreamEndpoints', () => { }, ], ) - await client.ensureDisconnected() + await client.disconnect() }, 15000) }) From 0a7607777426b5337b21e656153daf0522916eb4 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 18 Sep 2020 13:47:56 -0400 Subject: [PATCH 061/517] Improve authFetch logging. --- src/rest/authFetch.js | 25 +++++++++++++++++-------- test/unit/utils.test.js | 4 ++-- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/rest/authFetch.js b/src/rest/authFetch.js index 54fa359b8..fdc6cc6e1 100644 --- a/src/rest/authFetch.js +++ b/src/rest/authFetch.js @@ -1,5 +1,5 @@ import fetch from 'node-fetch' -import debugFactory from 'debug' +import Debug from 'debug' import AuthFetchError from '../errors/AuthFetchError' import { getVersionString } from '../utils' @@ -8,14 +8,20 @@ export const DEFAULT_HEADERS = { 'Streamr-Client': `streamr-client-javascript/${getVersionString()}`, } -const debug = debugFactory('StreamrClient:utils') +const debug = Debug('StreamrClient:utils:authfetch') + +let ID = 0 + +export default async function authFetch(url, session, opts, requireNewToken = false) { + ID += 1 + const timeStart = Date.now() + const id = ID -const authFetch = async (url, session, opts = {}, requireNewToken = false) => { const options = { ...opts, headers: { ...DEFAULT_HEADERS, - ...opts.headers, + ...(opts && opts.headers), } } // add default 'Content-Type: application/json' header for all POST and PUT requests @@ -23,7 +29,7 @@ const authFetch = async (url, session, opts = {}, requireNewToken = false) => { options.headers['Content-Type'] = 'application/json' } - debug('authFetch: ', url, opts) + debug('%d %s >> %o', id, url, opts) const response = await fetch(url, { ...opts, @@ -34,6 +40,8 @@ const authFetch = async (url, session, opts = {}, requireNewToken = false) => { ...options.headers, }, }) + const timeEnd = Date.now() + debug('%d %s << %d %s %s %s', id, url, response.status, response.statusText, Debug.humanize(timeEnd - timeStart)) const body = await response.text() @@ -41,13 +49,14 @@ const authFetch = async (url, session, opts = {}, requireNewToken = false) => { try { return JSON.parse(body || '{}') } catch (e) { + debug('%d %s – failed to parse body: %s', id, url, e.stack) throw new AuthFetchError(e.message, response, body) } } else if ([400, 401].includes(response.status) && !requireNewToken) { + debug('%d %s – revalidating session') return authFetch(url, session, options, true) } else { - throw new AuthFetchError(`Request to ${url} returned with error code ${response.status}.`, response, body) + debug('%d %s – failed', id, url) + throw new AuthFetchError(`Request ${id} to ${url} returned with error code ${response.status}.`, response, body) } } - -export default authFetch diff --git a/test/unit/utils.test.js b/test/unit/utils.test.js index 9f77db839..ac1541893 100644 --- a/test/unit/utils.test.js +++ b/test/unit/utils.test.js @@ -53,8 +53,8 @@ describe('utils', () => { session.getSessionToken = sinon.stub().resolves('invalid token') return authFetch(baseUrl + testUrl, session).catch((err) => { expect(session.getSessionToken.calledTwice).toBeTruthy() - expect(err.toString()).toEqual( - `Error: Request to ${baseUrl + testUrl} returned with error code 401. Unauthorized` + expect(err.toString()).toMatch( + `${baseUrl + testUrl} returned with error code 401. Unauthorized` ) expect(err.body).toEqual('Unauthorized') done() From 4fed34eee181a685ee71eb09977b93a4f4dbafae Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 21 Sep 2020 09:39:12 -0400 Subject: [PATCH 062/517] Update message sequencer to strictly enforce message order. --- package-lock.json | 74 ++++++++- package.json | 1 + src/Publisher.js | 217 ++++++++++++++++++-------- test/unit/MessageCreationUtil.test.js | 76 +++++++-- 4 files changed, 281 insertions(+), 87 deletions(-) diff --git a/package-lock.json b/package-lock.json index 833bdfcb4..2cfeb474d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6908,6 +6908,17 @@ "requires": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" + }, + "dependencies": { + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + } } }, "imurmurhash": { @@ -9695,6 +9706,17 @@ "dev": true, "requires": { "p-limit": "^2.0.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } } }, "path-exists": { @@ -10479,10 +10501,9 @@ "dev": true }, "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", + "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", "requires": { "p-try": "^2.0.0" } @@ -10494,6 +10515,17 @@ "dev": true, "requires": { "p-limit": "^2.2.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } } }, "p-map": { @@ -10524,8 +10556,7 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, "pac-proxy-agent": { "version": "3.0.1", @@ -13382,6 +13413,15 @@ "minimist": "^1.2.5" } }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, "p-locate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", @@ -13583,6 +13623,17 @@ "dev": true, "requires": { "p-limit": "^2.0.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } } }, "path-exists": { @@ -14057,6 +14108,17 @@ "dev": true, "requires": { "p-limit": "^2.0.0" + }, + "dependencies": { + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + } } }, "path-exists": { diff --git a/package.json b/package.json index 7c4c5d609..de90dbae0 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "node-fetch": "^2.6.1", "node-webcrypto-ossl": "^2.1.1", "once": "^1.4.0", + "p-limit": "^3.0.2", "p-memoize": "^4.0.0", "promise-memoize": "^1.2.1", "qs": "^6.9.4", diff --git a/src/Publisher.js b/src/Publisher.js index 939c94c58..f81b68d56 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -2,6 +2,8 @@ import crypto from 'crypto' import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import randomstring from 'randomstring' +import pLimit from 'p-limit' +import LRU from 'quick-lru' import { ethers } from 'ethers' import Signer from './Signer' @@ -27,32 +29,104 @@ function hash(stringToHash) { return crypto.createHash('md5').update(stringToHash).digest() } -class OrderedMessageChainCreator { - constructor() { +/** + * Message Chain Sequencing + */ + +class MessageChainSequence { + constructor({ maxSize = 10000 } = {}) { this.msgChainId = randomstring.generate(20) - this.messageRefs = new Map() + this.messageRefs = new LRU({ + maxSize, // should never exceed this except in pathological cases + }) } - create({ streamId, streamPartition, timestamp, publisherId, }) { + /** + * Generate the next message MessageID + previous MessageRef for this message chain. + * Messages with same timestamp get incremented sequence numbers. + */ + + add({ streamId, streamPartition, timestamp, publisherId, }) { + // NOTE: adding back-dated (i.e. non-sequentially timestamped) messages may 'break' sequencing. + // i.e. we lose track of biggest sequence number whenever timestamp changes for stream id+partition combo + // so backdated messsages will start at sequence 0 again, regardless of the sequencing of existing messages. + // storage considers timestamp+sequence number unique, so the newer messages will clobber the older messages + // Not feasible to keep greatest sequence number for every millisecond timestamp so not sure a good way around this. const key = `${streamId}|${streamPartition}` const prevMsgRef = this.messageRefs.get(key) - const sequenceNumber = this.getNextSequenceNumber(key, timestamp) - const messageId = new MessageID(streamId, streamPartition, timestamp, sequenceNumber, publisherId, this.msgChainId) - this.messageRefs.set(key, new MessageRef(timestamp, sequenceNumber)) + const isSameTimestamp = prevMsgRef && prevMsgRef.timestamp === timestamp + // increment if timestamp the same, otherwise 0 + const nextSequenceNumber = isSameTimestamp ? prevMsgRef.sequenceNumber + 1 : 0 + const messageId = new MessageID(streamId, streamPartition, timestamp, nextSequenceNumber, publisherId, this.msgChainId) + // update latest timestamp + sequence for this streamId+partition (see note above about clobbering sequencing) + this.messageRefs.set(key, new MessageRef(timestamp, nextSequenceNumber)) return [messageId, prevMsgRef] } - getNextSequenceNumber(key, timestamp) { - if (!this.messageRefs.has(key)) { return 0 } - const prev = this.messageRefs.get(key) - if (timestamp !== prev.timestamp) { - return 0 - } - return prev.sequenceNumber + 1 + clear() { + this.messageRefs.clear() + } +} + +/** + * Computes appropriate stream partition + */ + +export class StreamPartitioner { + constructor(client) { + this.client = client + const cacheOptions = client.options.cache + this._getStreamPartitions = CacheAsyncFn(this._getStreamPartitions.bind(this), cacheOptions) + this.hash = CacheFn(hash, cacheOptions) } clear() { - this.messageRefs.clear() + this._getStreamPartitions.clear() + this.hash.clear() + } + + /** + * Get partition for given stream/streamId + partitionKey + */ + + async get(streamObjectOrId, partitionKey) { + const streamPartitions = await this.getStreamPartitions(streamObjectOrId) + return this.computeStreamPartition(streamPartitions, partitionKey) + } + + async getStreamPartitions(streamObjectOrId) { + if (streamObjectOrId && streamObjectOrId.partitions != null) { + return streamObjectOrId.partitions + } + + // get streamId here so caching based on id works + const streamId = getStreamId(streamObjectOrId) + return this._getStreamPartitions(streamId) + } + + async _getStreamPartitions(streamId) { + const { partitions } = await this.client.getStream(streamId) + return partitions + } + + computeStreamPartition(partitionCount, partitionKey) { + if (!(Number.isSafeInteger(partitionCount) && partitionCount > 0)) { + throw new Error(`partitionCount is not a safe positive integer! ${partitionCount}`) + } + + if (partitionCount === 1) { + // Fast common case + return 0 + } + + if (!partitionKey) { + // Fallback to random partition if no key + return Math.floor(Math.random() * partitionCount) + } + + const buffer = this.hash(partitionKey) + const intHash = buffer.readInt32LE() + return Math.abs(intHash) % partitionCount } } @@ -60,21 +134,20 @@ export class MessageCreationUtil { constructor(client) { this.client = client const cacheOptions = client.options.cache - this.msgChainer = new OrderedMessageChainCreator(cacheOptions) - - this._getStreamPartitions = CacheAsyncFn(this._getStreamPartitions.bind(this), cacheOptions) + this.msgChainer = new MessageChainSequence(cacheOptions) + this.partitioner = new StreamPartitioner(client) this.getUserInfo = CacheAsyncFn(this.getUserInfo.bind(this), cacheOptions) this.getPublisherId = CacheAsyncFn(this.getPublisherId.bind(this), cacheOptions) - this.hash = CacheFn(hash, cacheOptions) + this.pending = new Map() } stop() { this.msgChainer.clear() - this.msgChainer = new OrderedMessageChainCreator() this.getUserInfo.clear() this.getPublisherId.clear() - this._getStreamPartitions.clear() - this.hash.clear() + this.partitioner.clear() + this.pending.forEach((p) => p.clearQueue()) + this.pending.clear() } async getPublisherId() { @@ -104,57 +177,64 @@ export class MessageCreationUtil { } async getUsername() { - return this.getUserInfo().then((userInfo) => ( - userInfo.username - || userInfo.id // In the edge case where StreamrClient.auth.apiKey is an anonymous key, userInfo.id is that anonymous key - )) + const { username, id } = await this.client.getUserInfo() + return ( + username + // edge case: if auth.apiKey is an anonymous key, userInfo.id is that anonymous key + || id + ) } - async getStreamPartitions(streamObjectOrId) { - if (streamObjectOrId && streamObjectOrId.partitions != null) { - return streamObjectOrId.partitions + async createStreamMessage(streamObjectOrId, options = {}) { + const { content } = options + // Validate content + if (typeof content !== 'object') { + throw new Error(`Message content must be an object! Was: ${content}`) } - // get streamId here so caching based on id works - const streamId = getStreamId(streamObjectOrId) - return this._getStreamPartitions(streamId) + // queued depdendencies fetching + const [publisherId, streamPartition] = await this._getDependencies(streamObjectOrId, options) + return this._createStreamMessage(getStreamId(streamObjectOrId), { + publisherId, + streamPartition, + ...options + }) } - async _getStreamPartitions(streamId) { - const { partitions } = await this.client.getStream(streamId) - return partitions - } + /** + * Fetch async dependencies for publishing. + * Should resolve in call-order per-stream + timestamp to guarantee correct sequencing. + */ - computeStreamPartition(partitionCount, partitionKey) { - if (!partitionCount) { - throw new Error('partitionCount is falsey!') - } else if (partitionCount === 1) { - // Fast common case - return 0 - } else if (partitionKey) { - const buffer = this.hash(partitionKey) - const intHash = buffer.readInt32LE() - return Math.abs(intHash) % partitionCount - } else { - // Fallback to random partition if no key - return Math.floor(Math.random() * partitionCount) - } - } + async _getDependencies(streamObjectOrId, { partitionKey, timestamp }) { + // This queue guarantees stream messages for the same timestamp are sequenced in-order + // regardless of the async resolution order. + // otherwise, if async calls happen to resolve in a different order + // than they were issued we will end up generating the wrong sequence numbers + const streamId = getStreamId(streamObjectOrId) + const key = `${streamId}|${timestamp}` + const queue = this.pending.get(key) || this.pending.set(key, pLimit(1)).get(key) - async createStreamMessage(streamObjectOrId, { data, timestamp, partitionKey } = {}) { - // Validate data - if (typeof data !== 'object') { - throw new Error(`Message data must be an object! Was: ${data}`) + try { + return await queue(() => ( + Promise.all([ + this.getPublisherId(), + this.partitioner.get(streamObjectOrId, partitionKey), + ]) + )) + } finally { + if (!queue.activeCount && !queue.pendingCount) { + this.pending.delete(key) + } } + } - const streamId = getStreamId(streamObjectOrId) - const [streamPartitions, publisherId] = await Promise.all([ - this.getStreamPartitions(streamObjectOrId), - this.getPublisherId(), - ]) + _createStreamMessage(streamId, options = {}) { + const { + content, streamPartition, timestamp, publisherId, ...opts + } = options - const streamPartition = this.computeStreamPartition(streamPartitions, partitionKey) - const [messageId, prevMsgRef] = this.msgChainer.create({ + const [messageId, prevMsgRef] = this.msgChainer.add({ streamId, streamPartition, timestamp, @@ -164,7 +244,8 @@ export class MessageCreationUtil { return new StreamMessage({ messageId, prevMsgRef, - content: data, + content, + ...opts }) } } @@ -197,19 +278,19 @@ export default class Publisher { }) } - async _publish(streamObjectOrId, data, timestamp = new Date(), partitionKey = null) { + async _publish(streamObjectOrId, content, timestamp = new Date(), partitionKey = null) { if (this.client.session.isUnauthenticated()) { throw new Error('Need to be authenticated to publish.') } const timestampAsNumber = timestamp instanceof Date ? timestamp.getTime() : new Date(timestamp).getTime() - const [sessionToken, streamMessage] = await Promise.all([ - this.client.session.getSessionToken(), + const [streamMessage, sessionToken] = await Promise.all([ this.msgCreationUtil.createStreamMessage(streamObjectOrId, { - data, + content, timestamp: timestampAsNumber, partitionKey }), + this.client.session.getSessionToken(), ]) if (this.signer) { @@ -228,7 +309,7 @@ export default class Publisher { const streamId = getStreamId(streamObjectOrId) throw new FailedToPublishError( streamId, - data, + content, err ) } diff --git a/test/unit/MessageCreationUtil.test.js b/test/unit/MessageCreationUtil.test.js index e3fb43f5c..eec0539f2 100644 --- a/test/unit/MessageCreationUtil.test.js +++ b/test/unit/MessageCreationUtil.test.js @@ -1,8 +1,9 @@ import sinon from 'sinon' import { ethers } from 'ethers' +import { wait } from 'streamr-test-utils' import { MessageLayer } from 'streamr-client-protocol' -import { MessageCreationUtil } from '../../src/Publisher' +import { MessageCreationUtil, StreamPartitioner } from '../../src/Publisher' import Stream from '../../src/rest/domain/Stream' // eslint-disable-next-line import/no-named-as-default-member @@ -84,24 +85,26 @@ describe('MessageCreationUtil', () => { }) }) - describe('partitioner', () => { + describe('StreamPartitioner', () => { + let streamPartitioner + beforeAll(() => { client = createClient() }) beforeEach(() => { - msgCreationUtil = new MessageCreationUtil(client) + streamPartitioner = new StreamPartitioner(client) }) it('should throw if partition count is not defined', () => { expect(() => { - msgCreationUtil.computeStreamPartition(undefined, 'foo') + streamPartitioner.computeStreamPartition(undefined, 'foo') }).toThrow() }) it('should always return partition 0 for all keys if partition count is 1', () => { for (let i = 0; i < 100; i++) { - expect(msgCreationUtil.computeStreamPartition(1, `foo${i}`)).toEqual(0) + expect(streamPartitioner.computeStreamPartition(1, `foo${i}`)).toEqual(0) } }) @@ -119,7 +122,7 @@ describe('MessageCreationUtil', () => { expect(correctResults.length).toEqual(keys.length) for (let i = 0; i < keys.length; i++) { - const partition = msgCreationUtil.computeStreamPartition(10, keys[i]) + const partition = streamPartitioner.computeStreamPartition(10, keys[i]) expect(correctResults[i]).toStrictEqual(partition) } }) @@ -165,7 +168,7 @@ describe('MessageCreationUtil', () => { for (let i = 0; i < 10; i++) { // eslint-disable-next-line no-await-in-loop const streamMessage = await msgCreationUtil.createStreamMessage(stream, { - data: pubMsg, timestamp: ts, + content: pubMsg, timestamp: ts, }) expect(streamMessage).toStrictEqual(getStreamMessage('streamId', ts, i, prevMsgRef)) prevMsgRef = new MessageRef(ts, i) @@ -178,13 +181,60 @@ describe('MessageCreationUtil', () => { for (let i = 0; i < 10; i++) { // eslint-disable-next-line no-await-in-loop const streamMessage = await msgCreationUtil.createStreamMessage(stream, { - data: pubMsg, timestamp: ts + i, + content: pubMsg, timestamp: ts + i, }) expect(streamMessage).toStrictEqual(getStreamMessage('streamId', ts + i, 0, prevMsgRef)) prevMsgRef = new MessageRef(ts + i, 0) } }) + it('should sequence in order even if async dependencies resolve out of order', async () => { + const ts = Date.now() + let calls = 0 + const getStreamPartitions = msgCreationUtil.partitioner.getStreamPartitions.bind(msgCreationUtil.partitioner) + msgCreationUtil.partitioner.getStreamPartitions = async (...args) => { + calls += 1 + if (calls === 2) { + const result = await getStreamPartitions(...args) + // delay resolving this call + await wait(100) + return result + } + return getStreamPartitions(...args) + } + + // simultaneously publish 3 messages, but the second item's dependencies will resolve late. + // this shouldn't affect the sequencing + const messages = await Promise.all([ + msgCreationUtil.createStreamMessage(stream, { + content: pubMsg, timestamp: ts, + }), + msgCreationUtil.createStreamMessage(stream, { + content: pubMsg, timestamp: ts, + }), + msgCreationUtil.createStreamMessage(stream, { + content: pubMsg, timestamp: ts, + }) + ]) + const sequenceNumbers = messages.map((m) => m.getSequenceNumber()) + expect(sequenceNumbers).toEqual([0, 1, 2]) + }) + + it('should handle old timestamps', async () => { + const ts = Date.now() + const streamMessage1 = await msgCreationUtil.createStreamMessage(stream, { + content: pubMsg, timestamp: ts, + }) + const streamMessage2 = await msgCreationUtil.createStreamMessage(stream, { + content: pubMsg, timestamp: ts + 1, + }) + const streamMessage3 = await msgCreationUtil.createStreamMessage(stream, { + content: pubMsg, timestamp: ts, + }) + const sequenceNumbers = [streamMessage1, streamMessage2, streamMessage3].map((m) => m.getSequenceNumber()) + expect(sequenceNumbers).toEqual([0, 0, 1]) + }) + it('should publish messages with sequence number 0 (different streams)', async () => { const ts = Date.now() const stream2 = new Stream(null, { @@ -197,13 +247,13 @@ describe('MessageCreationUtil', () => { }) const msg1 = await msgCreationUtil.createStreamMessage(stream, { - data: pubMsg, timestamp: ts + content: pubMsg, timestamp: ts }) const msg2 = await msgCreationUtil.createStreamMessage(stream2, { - data: pubMsg, timestamp: ts + content: pubMsg, timestamp: ts }) const msg3 = await msgCreationUtil.createStreamMessage(stream3, { - data: pubMsg, timestamp: ts + content: pubMsg, timestamp: ts }) expect(msg1).toEqual(getStreamMessage('streamId', ts, 0, null)) @@ -213,7 +263,7 @@ describe('MessageCreationUtil', () => { it.skip('should sign messages if signer is defined', async () => { const msg1 = await msgCreationUtil.createStreamMessage(stream, { - data: pubMsg, timestamp: Date.now() + content: pubMsg, timestamp: Date.now() }) expect(msg1.signature).toBe('signature') }) @@ -221,7 +271,7 @@ describe('MessageCreationUtil', () => { it('should create message from a stream id by fetching the stream', async () => { const ts = Date.now() const streamMessage = await msgCreationUtil.createStreamMessage(stream.id, { - data: pubMsg, timestamp: ts + content: pubMsg, timestamp: ts }) expect(streamMessage).toEqual(getStreamMessage(stream.id, ts, 0, null)) }) From b8aec5f291c16ea547e8e1946bb1e6bfab855fbf Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 21 Sep 2020 13:54:28 -0400 Subject: [PATCH 063/517] Queue publishes per-stream otherwise can skip forward then back even when messages published in correct sequence. --- src/Publisher.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Publisher.js b/src/Publisher.js index f81b68d56..05412d920 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -206,15 +206,14 @@ export class MessageCreationUtil { * Should resolve in call-order per-stream + timestamp to guarantee correct sequencing. */ - async _getDependencies(streamObjectOrId, { partitionKey, timestamp }) { + async _getDependencies(streamObjectOrId, { partitionKey }) { // This queue guarantees stream messages for the same timestamp are sequenced in-order // regardless of the async resolution order. // otherwise, if async calls happen to resolve in a different order // than they were issued we will end up generating the wrong sequence numbers const streamId = getStreamId(streamObjectOrId) - const key = `${streamId}|${timestamp}` + const key = streamId const queue = this.pending.get(key) || this.pending.set(key, pLimit(1)).get(key) - try { return await queue(() => ( Promise.all([ @@ -224,6 +223,7 @@ export class MessageCreationUtil { )) } finally { if (!queue.activeCount && !queue.pendingCount) { + // clean up this.pending.delete(key) } } From be12a3b9e42b3c54507ef4d8ebf7437ce58cc0c9 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 21 Sep 2020 14:46:47 -0400 Subject: [PATCH 064/517] Add partial solution to broken backdated messages, at least doesn't break regular sequential publishes. --- src/Publisher.js | 15 +- test/integration/Sequencing.test.js | 290 ++++++++++++++++++++++++++ test/unit/MessageCreationUtil.test.js | 2 +- 3 files changed, 302 insertions(+), 5 deletions(-) create mode 100644 test/integration/Sequencing.test.js diff --git a/src/Publisher.js b/src/Publisher.js index 05412d920..8769800b2 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -47,19 +47,26 @@ class MessageChainSequence { */ add({ streamId, streamPartition, timestamp, publisherId, }) { - // NOTE: adding back-dated (i.e. non-sequentially timestamped) messages may 'break' sequencing. + // NOTE: publishing back-dated (i.e. non-sequentially timestamped) messages will 'break' sequencing. // i.e. we lose track of biggest sequence number whenever timestamp changes for stream id+partition combo - // so backdated messsages will start at sequence 0 again, regardless of the sequencing of existing messages. + // so backdated messages will start at sequence 0 again, regardless of the sequencing of existing messages. // storage considers timestamp+sequence number unique, so the newer messages will clobber the older messages // Not feasible to keep greatest sequence number for every millisecond timestamp so not sure a good way around this. + // Possible we should keep a global sequence number const key = `${streamId}|${streamPartition}` const prevMsgRef = this.messageRefs.get(key) const isSameTimestamp = prevMsgRef && prevMsgRef.timestamp === timestamp + const isBackdated = prevMsgRef && prevMsgRef.timestamp > timestamp // increment if timestamp the same, otherwise 0 const nextSequenceNumber = isSameTimestamp ? prevMsgRef.sequenceNumber + 1 : 0 const messageId = new MessageID(streamId, streamPartition, timestamp, nextSequenceNumber, publisherId, this.msgChainId) - // update latest timestamp + sequence for this streamId+partition (see note above about clobbering sequencing) - this.messageRefs.set(key, new MessageRef(timestamp, nextSequenceNumber)) + // update latest timestamp + sequence for this streamId+partition + // (see note above about clobbering sequencing) + // don't update latest if timestamp < previous timestamp + // this "fixes" the sequence breaking issue above, but this message will silently disappear + if (!isBackdated) { + this.messageRefs.set(key, new MessageRef(timestamp, nextSequenceNumber)) + } return [messageId, prevMsgRef] } diff --git a/test/integration/Sequencing.test.js b/test/integration/Sequencing.test.js new file mode 100644 index 000000000..93a448eb9 --- /dev/null +++ b/test/integration/Sequencing.test.js @@ -0,0 +1,290 @@ +import { wait, waitForCondition, waitForEvent } from 'streamr-test-utils' + +import { uid, fakePrivateKey } from '../utils' +import StreamrClient from '../../src' +import Connection from '../../src/Connection' + +import config from './config' + +const Msg = (opts) => ({ + value: uid('msg'), + ...opts, +}) + +function toSeq(requests, ts = Date.now()) { + return requests.map((m) => { + const { prevMsgRef } = m.streamMessage + return [ + [m.streamMessage.getTimestamp() - ts, m.streamMessage.getSequenceNumber()], + prevMsgRef ? [prevMsgRef.timestamp - ts, prevMsgRef.sequenceNumber] : null + ] + }) +} + +describe('Sequencing', () => { + let expectErrors = 0 // check no errors by default + let onError = jest.fn() + let client + let stream + + const createClient = (opts = {}) => { + const c = new StreamrClient({ + auth: { + privateKey: fakePrivateKey(), + }, + autoConnect: false, + autoDisconnect: false, + maxRetries: 2, + ...config.clientOptions, + ...opts, + }) + c.onError = jest.fn() + c.on('error', onError) + return c + } + + beforeEach(async () => { + expectErrors = 0 + onError = jest.fn() + client = createClient() + await client.connect() + + stream = await client.createStream({ + name: uid('stream') + }) + }) + + afterEach(async () => { + await wait() + // ensure no unexpected errors + expect(onError).toHaveBeenCalledTimes(expectErrors) + if (client) { + expect(client.onError).toHaveBeenCalledTimes(expectErrors) + } + }) + + afterEach(async () => { + await wait() + if (client) { + client.debug('disconnecting after test') + await client.disconnect() + } + + const openSockets = Connection.getOpen() + if (openSockets !== 0) { + throw new Error(`sockets not closed: ${openSockets}`) + } + }) + + it('should sequence in order', async () => { + const ts = Date.now() + const msgsPublished = [] + const msgsReceieved = [] + + await client.subscribe(stream.id, (m) => msgsReceieved.push(m)) + + const nextMsg = () => { + const msg = Msg() + msgsPublished.push(msg) + return msg + } + + const requests = await Promise.all([ + // first 2 messages at ts + 0 + client.publish(stream, nextMsg(), ts), + client.publish(stream, nextMsg(), ts), + // next two messages at ts + 1 + client.publish(stream, nextMsg(), ts + 1), + client.publish(stream, nextMsg(), ts + 1), + ]) + const seq = toSeq(requests, ts) + expect(seq).toEqual([ + [[0, 0], null], + [[0, 1], [0, 0]], + [[1, 0], [0, 1]], + [[1, 1], [1, 0]], + ]) + + await waitForCondition(() => ( + msgsReceieved.length === msgsPublished.length + ), 5000).catch(() => {}) // ignore, tests will fail anyway + + expect(msgsReceieved).toEqual(msgsPublished) + }, 10000) + + it('should sequence in order even if some calls delayed', async () => { + const ts = Date.now() + const msgsPublished = [] + const msgsReceieved = [] + + let calls = 0 + const { clear } = client.publisher.msgCreationUtil.getPublisherId + const getPublisherId = client.publisher.msgCreationUtil.getPublisherId.bind(client.publisher.msgCreationUtil) + client.publisher.msgCreationUtil.getPublisherId = async (...args) => { + // delay getPublisher call + calls += 1 + if (calls === 2) { + const result = await getPublisherId(...args) + // delay resolving this call + await wait(100) + return result + } + return getPublisherId(...args) + } + client.publisher.msgCreationUtil.getPublisherId.clear = clear + + const nextMsg = () => { + const msg = Msg() + msgsPublished.push(msg) + return msg + } + + await client.subscribe(stream.id, (m) => msgsReceieved.push(m)) + const requests = await Promise.all([ + // first 2 messages at ts + 0 + client.publish(stream, nextMsg(), ts), + client.publish(stream, nextMsg(), ts), + // next two messages at ts + 1 + client.publish(stream, nextMsg(), ts + 1), + client.publish(stream, nextMsg(), ts + 1), + ]) + const seq = toSeq(requests, ts) + expect(seq).toEqual([ + [[0, 0], null], + [[0, 1], [0, 0]], + [[1, 0], [0, 1]], + [[1, 1], [1, 0]], + ]) + + await waitForCondition(() => ( + msgsReceieved.length === msgsPublished.length + ), 5000).catch(() => {}) // ignore, tests will fail anyway + + expect(msgsReceieved).toEqual(msgsPublished) + }, 10000) + + it('should sequence in order even if publish requests backdated', async () => { + const ts = Date.now() + const msgsPublished = [] + const msgsReceieved = [] + + await client.subscribe(stream.id, (m) => msgsReceieved.push(m)) + + const nextMsg = (...args) => { + const msg = Msg(...args) + msgsPublished.push(msg) + return msg + } + + const requests = await Promise.all([ + // publish at ts + 0 + client.publish(stream, nextMsg(), ts), + // publish at ts + 1 + client.publish(stream, nextMsg(), ts + 1), + // backdate at ts + 0 + client.publish(stream, nextMsg({ + backdated: true, + }), ts), + // resume at ts + 2 + client.publish(stream, nextMsg(), ts + 2), + client.publish(stream, nextMsg(), ts + 2), + client.publish(stream, nextMsg(), ts + 3), + ]) + + await waitForCondition(() => ( + msgsReceieved.length === msgsPublished.length + ), 2000).catch(() => {}) // ignore, tests will fail anyway + + const msgsResent = [] + const sub = await client.resend({ + stream: stream.id, + resend: { + from: { + timestamp: 0 + }, + }, + }, (m) => msgsResent.push(m)) + await waitForEvent(sub, 'resent') + + expect(msgsReceieved).toEqual(msgsResent) + // backdated messages disappear + expect(msgsReceieved).toEqual(msgsPublished.filter(({ backdated }) => !backdated)) + + const seq = toSeq(requests, ts) + client.debug(seq) + expect(seq).toEqual([ + [[0, 0], null], + [[1, 0], [0, 0]], + [[0, 0], [1, 0]], // bad message + [[2, 0], [1, 0]], + [[2, 1], [2, 0]], + [[3, 0], [2, 1]], + ]) + }, 10000) + + it('should sequence in order even if publish requests backdated in sequence', async () => { + const ts = Date.now() + const msgsPublished = [] + const msgsReceieved = [] + + await client.subscribe(stream.id, (m) => msgsReceieved.push(m)) + + const nextMsg = (...args) => { + const msg = Msg(...args) + msgsPublished.push(msg) + return msg + } + + const requests = await Promise.all([ + // first 3 messages at ts + 0 + client.publish(stream, nextMsg(), ts), + client.publish(stream, nextMsg(), ts), + client.publish(stream, nextMsg(), ts), + // next two messages at ts + 1 + client.publish(stream, nextMsg(), ts + 1), + client.publish(stream, nextMsg(), ts + 1), + // backdate at ts + 0 + client.publish(stream, nextMsg({ + backdated: true, + }), ts), + // resume publishing at ts + 1 + client.publish(stream, nextMsg(), ts + 1), + client.publish(stream, nextMsg(), ts + 1), + client.publish(stream, nextMsg(), ts + 2), + client.publish(stream, nextMsg(), ts + 2), + ]) + + await waitForCondition(() => ( + msgsReceieved.length === msgsPublished.length + ), 2000).catch(() => {}) // ignore, tests will fail anyway + + const msgsResent = [] + const sub = await client.resend({ + stream: stream.id, + resend: { + from: { + timestamp: 0 + }, + }, + }, (m) => msgsResent.push(m)) + await waitForEvent(sub, 'resent') + + expect(msgsReceieved).toEqual(msgsResent) + // backdated messages disappear + expect(msgsReceieved).toEqual(msgsPublished.filter(({ backdated }) => !backdated)) + + const seq = toSeq(requests, ts) + expect(seq).toEqual([ + [[0, 0], null], + [[0, 1], [0, 0]], + [[0, 2], [0, 1]], + [[1, 0], [0, 2]], + [[1, 1], [1, 0]], + [[0, 0], [1, 1]], // bad message + [[1, 2], [1, 1]], + [[1, 3], [1, 2]], + [[2, 0], [1, 3]], + [[2, 1], [2, 0]], + ]) + }, 10000) +}) diff --git a/test/unit/MessageCreationUtil.test.js b/test/unit/MessageCreationUtil.test.js index eec0539f2..dfb46b3ba 100644 --- a/test/unit/MessageCreationUtil.test.js +++ b/test/unit/MessageCreationUtil.test.js @@ -232,7 +232,7 @@ describe('MessageCreationUtil', () => { content: pubMsg, timestamp: ts, }) const sequenceNumbers = [streamMessage1, streamMessage2, streamMessage3].map((m) => m.getSequenceNumber()) - expect(sequenceNumbers).toEqual([0, 0, 1]) + expect(sequenceNumbers).toEqual([0, 0, 0]) }) it('should publish messages with sequence number 0 (different streams)', async () => { From 693b3e7e61f994f95d3ba61d35dce2ca7b01e1a1 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 21 Sep 2020 15:53:53 -0400 Subject: [PATCH 065/517] Tidy up, add some comments. --- src/Publisher.js | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/Publisher.js b/src/Publisher.js index 8769800b2..f27487b5a 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -36,6 +36,7 @@ function hash(stringToHash) { class MessageChainSequence { constructor({ maxSize = 10000 } = {}) { this.msgChainId = randomstring.generate(20) + // tracks previous timestamp+sequence for stream+partition this.messageRefs = new LRU({ maxSize, // should never exceed this except in pathological cases }) @@ -145,7 +146,7 @@ export class MessageCreationUtil { this.partitioner = new StreamPartitioner(client) this.getUserInfo = CacheAsyncFn(this.getUserInfo.bind(this), cacheOptions) this.getPublisherId = CacheAsyncFn(this.getPublisherId.bind(this), cacheOptions) - this.pending = new Map() + this.pending = new Map() // stores an async queue for each stream's async deps } stop() { @@ -210,7 +211,7 @@ export class MessageCreationUtil { /** * Fetch async dependencies for publishing. - * Should resolve in call-order per-stream + timestamp to guarantee correct sequencing. + * Should resolve in call-order per-stream to guarantee correct sequencing. */ async _getDependencies(streamObjectOrId, { partitionKey }) { @@ -236,6 +237,10 @@ export class MessageCreationUtil { } } + /** + * Synchronously generate chain sequence + stream message after async deps resolved. + */ + _createStreamMessage(streamId, options = {}) { const { content, streamPartition, timestamp, publisherId, ...opts @@ -277,8 +282,7 @@ export default class Publisher { } async publish(...args) { - this.debug('publish()') - + // wrap publish in error emitter return this._publish(...args).catch((err) => { this.onErrorEmit(err) throw err @@ -286,21 +290,27 @@ export default class Publisher { } async _publish(streamObjectOrId, content, timestamp = new Date(), partitionKey = null) { + this.debug('publish()') if (this.client.session.isUnauthenticated()) { throw new Error('Need to be authenticated to publish.') } const timestampAsNumber = timestamp instanceof Date ? timestamp.getTime() : new Date(timestamp).getTime() + // get session + generate stream message + // important: stream message call must be executed in publish() call order + // or sequencing will be broken. + // i.e. do not put async work before call to createStreamMessage const [streamMessage, sessionToken] = await Promise.all([ this.msgCreationUtil.createStreamMessage(streamObjectOrId, { content, timestamp: timestampAsNumber, partitionKey }), - this.client.session.getSessionToken(), + this.client.session.getSessionToken(), // fetch in parallel ]) if (this.signer) { + // optional await this.signer.signStreamMessage(streamMessage) } @@ -310,6 +320,7 @@ export default class Publisher { requestId, sessionToken, }) + try { await this.client.send(request) } catch (err) { @@ -320,10 +331,11 @@ export default class Publisher { err ) } + return request } - getPublisherId() { + async getPublisherId() { if (this.client.session.isUnauthenticated()) { throw new Error('Need to be authenticated to getPublisherId.') } From 132fabfcbe96465a6737f154dd977e48fe4e2d7b Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 22 Sep 2020 10:02:02 -0400 Subject: [PATCH 066/517] Move publish queue fn into utils. --- src/Publisher.js | 29 +++++++-------------- src/utils.js | 68 ++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 75 insertions(+), 22 deletions(-) diff --git a/src/Publisher.js b/src/Publisher.js index f27487b5a..94dd67749 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -2,14 +2,13 @@ import crypto from 'crypto' import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import randomstring from 'randomstring' -import pLimit from 'p-limit' import LRU from 'quick-lru' import { ethers } from 'ethers' import Signer from './Signer' import Stream from './rest/domain/Stream' import FailedToPublishError from './errors/FailedToPublishError' -import { CacheAsyncFn, CacheFn } from './utils' +import { CacheAsyncFn, CacheFn, LimitAsyncFnByKey } from './utils' const { StreamMessage, MessageID, MessageRef } = MessageLayer @@ -146,7 +145,7 @@ export class MessageCreationUtil { this.partitioner = new StreamPartitioner(client) this.getUserInfo = CacheAsyncFn(this.getUserInfo.bind(this), cacheOptions) this.getPublisherId = CacheAsyncFn(this.getPublisherId.bind(this), cacheOptions) - this.pending = new Map() // stores an async queue for each stream's async deps + this.queue = LimitAsyncFnByKey(1) // an async queue for each stream's async deps } stop() { @@ -154,8 +153,7 @@ export class MessageCreationUtil { this.getUserInfo.clear() this.getPublisherId.clear() this.partitioner.clear() - this.pending.forEach((p) => p.clearQueue()) - this.pending.clear() + this.queue.clear() } async getPublisherId() { @@ -220,21 +218,12 @@ export class MessageCreationUtil { // otherwise, if async calls happen to resolve in a different order // than they were issued we will end up generating the wrong sequence numbers const streamId = getStreamId(streamObjectOrId) - const key = streamId - const queue = this.pending.get(key) || this.pending.set(key, pLimit(1)).get(key) - try { - return await queue(() => ( - Promise.all([ - this.getPublisherId(), - this.partitioner.get(streamObjectOrId, partitionKey), - ]) - )) - } finally { - if (!queue.activeCount && !queue.pendingCount) { - // clean up - this.pending.delete(key) - } - } + return this.queue(streamId, async () => ( + Promise.all([ + this.getPublisherId(), + this.partitioner.get(streamObjectOrId, partitionKey), + ]) + )) } /** diff --git a/src/utils.js b/src/utils.js index 62410cfb0..7ad6ee4e7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -2,6 +2,7 @@ import { v4 as uuidv4 } from 'uuid' import uniqueId from 'lodash.uniqueid' import LRU from 'quick-lru' import pMemoize from 'p-memoize' +import pLimit from 'p-limit' import mem from 'mem' import pkg from '../package.json' @@ -44,12 +45,27 @@ export const getEndpointUrl = (baseUrl, ...pathParts) => { } /* eslint-disable object-curly-newline */ -export function CacheAsyncFn(fn, { + +/** + * Returns a cached async fn, cached keyed on first argument passed. See documentation for mem/p-memoize. + * Caches into a LRU cache capped at options.maxSize + * Won't call asyncFn again until options.maxAge or options.maxSize exceeded, or cachedAsyncFn.clear() is called. + * Won't cache rejections by default. Override with options.cachePromiseRejection = true. + * + * ```js + * const cachedAsyncFn = CacheAsyncFn(asyncFn, options) + * await cachedAsyncFn(key) + * await cachedAsyncFn(key) + * cachedAsyncFn.clear() + * ``` + */ + +export function CacheAsyncFn(asyncFn, { maxSize = 10000, maxAge = 30 * 60 * 1000, // 30 minutes cachePromiseRejection = false, } = {}) { - const cachedFn = pMemoize(fn, { + const cachedFn = pMemoize(asyncFn, { maxAge, cachePromiseRejection, cache: new LRU({ @@ -60,6 +76,20 @@ export function CacheAsyncFn(fn, { return cachedFn } +/** + * Returns a cached fn, cached keyed on first argument passed. See documentation for mem. + * Caches into a LRU cache capped at options.maxSize + * Won't call fn again until options.maxAge or options.maxSize exceeded, or cachedFn.clear() is called. + * + * ```js + * const cachedFn = CacheFn(fn, options) + * cachedFn(key) + * cachedFn(key) + * cachedFn(...args) + * cachedFn.clear() + * ``` + */ + export function CacheFn(fn, { maxSize = 10000, maxAge = 30 * 60 * 1000, // 30 minutes @@ -73,4 +103,38 @@ export function CacheFn(fn, { cachedFn.clear = () => mem.clear(cachedFn) return cachedFn } + /* eslint-enable object-curly-newline */ + +/** + * Returns a limit function that limits concurrency per-key. + * + * ```js + * const limit = LimitAsyncFnByKey(1) + * limit('channel1', fn) + * limit('channel2', fn) + * limit('channel2', fn) + * ``` + */ + +export function LimitAsyncFnByKey(limit) { + const pending = new Map() + const f = async (id, fn) => { + const limitFn = pending.get(id) || pending.set(id, pLimit(limit)).get(id) + try { + return await limitFn(fn) + } finally { + if (!limitFn.activeCount && !limitFn.pendingCount && pending.get(id) === limitFn) { + // clean up if no more active entries (if not cleared) + pending.delete(id) + } + } + } + f.clear = () => { + // note: does not cancel promises + pending.forEach((p) => p.clearQueue()) + pending.clear() + } + return f +} + From 8caef9987ecb21401dfe668d1c7740c3a8d56bd4 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 24 Sep 2020 13:32:31 -0400 Subject: [PATCH 067/517] Add PushQueue async iterator. --- .eslintrc.js | 5 +- package-lock.json | 5 + package.json | 1 + src/PushQueue.js | 155 ++++++++++++++++++++++ test/unit/PushQueue.test.js | 249 ++++++++++++++++++++++++++++++++++++ 5 files changed, 414 insertions(+), 1 deletion(-) create mode 100644 src/PushQueue.js create mode 100644 test/unit/PushQueue.test.js diff --git a/.eslintrc.js b/.eslintrc.js index 7fa74bcad..a28522a54 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,6 +24,9 @@ module.exports = { 'promise/always-return': 'warn', 'promise/catch-or-return': 'warn', 'require-atomic-updates': 'warn', - 'promise/param-names': 'warn' + 'promise/param-names': 'warn', + 'no-restricted-syntax': [ + 'error', 'ForInStatement', 'LabeledStatement', 'WithStatement' + ], } } diff --git a/package-lock.json b/package-lock.json index 2cfeb474d..4016648ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9989,6 +9989,11 @@ } } }, + "node-abort-controller": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-1.1.0.tgz", + "integrity": "sha512-dEYmUqjtbivotqjraOe8UvhT/poFfog1BQRNsZm/MSEDDESk2cQ1tvD8kGyuN07TM/zoW+n42odL8zTeJupYdQ==" + }, "node-addon-api": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", diff --git a/package.json b/package.json index de90dbae0..41ff2db84 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "eventemitter3": "^4.0.7", "lodash.uniqueid": "^4.0.1", "mem": "^6.1.1", + "node-abort-controller": "^1.1.0", "node-fetch": "^2.6.1", "node-webcrypto-ossl": "^2.1.1", "once": "^1.4.0", diff --git a/src/PushQueue.js b/src/PushQueue.js new file mode 100644 index 000000000..5b5116cf1 --- /dev/null +++ b/src/PushQueue.js @@ -0,0 +1,155 @@ +function createIterResult(value, done) { + return { + value, + done: !!done, + } +} + +class AbortError extends Error { + constructor(msg = '', ...args) { + super(`The operation was aborted. ${msg}`, ...args) + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor) + } + } +} + +/** + * Async Iterable PushQueue + * On throw/abort any items in buffer will be flushed before iteration throws. + * Heavily based on (upcoming) events.on API in node: + * @see https://github.com/nodejs/node/blob/e36ffb72bebae55091304da51837ca204367dc16/lib/events.js#L707-L826 + * + * ```js + * const queue = new PushQueue([item1], { + * signal: abortController.signal // optional + * }) + * queue.push(item2, item3) // supports pushing multiple at once + * setTimeout(() => { + * queue.push(item4) // push asynchronously, iterator will wait + * queue.return() // need to explicitly end iteration or it will continue forever + * queue.throw(err) // force the queue to throw + * abortController.abort() // alternative + * }) + * try { + * for await (const m of queue) { + * console.log(m) + * break // this calls await queue.return() + * } + * } catch (err) { + * // queue threw an error + * } finally { + * // run clean up after iteration success/error + * } + * ``` + */ + +export default class PushQueue { + constructor(items = [], { signal } = {}) { + this.buffer = [...items] + this.finished = false + this.error = null // queued error + this.nextQueue = [] // queued promises for next() + + this[Symbol.asyncIterator] = this[Symbol.asyncIterator].bind(this) + this.onAbort = this.onAbort.bind(this) + + // abort signal handling + this.signal = signal + if (signal) { + if (signal.aborted) { + this.onAbort() + } + + signal.addEventListener('abort', this.onAbort, { + once: true + }) + } + } + + onAbort() { + return this.throw(new AbortError()) + } + + async next() { + // always feed from buffer first + if (this.buffer.length) { + return createIterResult(this.buffer.shift()) + } + + // handle queued error + if (this.error) { + const err = this.error + this.error = null + throw err + } + + // done + if (this.finished) { + return createIterResult(undefined, true) + } + + // wait for next push + return new Promise((resolve, reject) => { + this.nextQueue.push({ + resolve, + reject, + }) + }) + } + + async return() { + this.finished = true + if (this.signal) { + this.signal.removeEventListener('abort', this.onAbort, { + once: true, + }) + } + + // clean up outstanding promises + for (const p of this.nextQueue) { + p.resolve(createIterResult(undefined, true)) + } + + return createIterResult(undefined, true) + } + + async throw(err) { + this.finished = true + const p = this.nextQueue.shift() + if (p) { + p.reject(err) + } else { + // for next() + this.error = err + } + + return this.return() + } + + get length() { + return this.buffer.length + } + + push(...values) { + if (this.finished) { + // do nothing if done + return + } + + const p = this.nextQueue.shift() + if (p) { + const [first, ...rest] = values + p.resolve(createIterResult(first)) + this.buffer.push(...rest) + } else { + this.buffer.push(...values) + } + } + + [Symbol.asyncIterator]() { + // NOTE: consider throwing if trying to iterate after finished + // or maybe returning a new iterator? + return this + } +} diff --git a/test/unit/PushQueue.test.js b/test/unit/PushQueue.test.js new file mode 100644 index 000000000..fc96efdff --- /dev/null +++ b/test/unit/PushQueue.test.js @@ -0,0 +1,249 @@ +import Debug from 'debug' +import { wait } from 'streamr-test-utils' +import AbortController from 'node-abort-controller' + +import PushQueue from '../../src/PushQueue' + +console.log = Debug('Streamr::console') + +describe('PushQueue', () => { + it('supports pre-buffering, async push & return', async () => { + const q = new PushQueue() + expect(q.length).toBe(0) + const items = [1, 2, 3, 4] + q.push(items[0]) + expect(q.length).toBe(1) + q.push(items[1]) + expect(q.length).toBe(2) + + setTimeout(() => { + // buffer should have drained by now + expect(q.length).toBe(0) + q.push(items[2]) + q.push(items[3]) + setTimeout(() => { + q.return(5) // both items above should get through + q.push('nope') // this should not + }, 20) + }, 10) + + let i = 0 + for await (const msg of q) { + expect(msg).toBe(items[i]) + i += 1 + } + + expect(i).toBe(4) + // buffer should have drained at end + expect(q.length).toBe(0) + }) + + it('supports passing initial values to constructor', async () => { + const q = new PushQueue(['a', 'b']) + expect(q.length).toBe(2) + + const msgs = [] + for await (const msg of q) { + msgs.push(msg) + if (!q.length) { + break // this calls await q.return() + } + } + + expect(msgs).toEqual(['a', 'b']) + + // buffer should have drained at end + expect(q.length).toBe(0) + + // these should have no effect + q.push('c') + await q.return() + }) + + it('consumes buffered items even after return', async () => { + const q = new PushQueue(['a', 'b']) + q.return() + const msgs = [] + for await (const msg of q) { + msgs.push(msg) + } + expect(msgs).toEqual(['a', 'b']) + }) + + it('handles iterating again', async () => { + const q = new PushQueue(['a', 'b']) + q.return() + const msgs = [] + for await (const msg of q) { + msgs.push(msg) + } + // can't iterate again after return + for await (const msg of q) { + throw new Error('should not get here ' + msg) + } + expect(msgs).toEqual(['a', 'b']) + }) + + it('supports passing multiple values to push', async () => { + const q = new PushQueue() + q.push('a', 'b') + const msgs = [] + for await (const msg of q) { + msgs.push(msg) + if (!q.length) { + break // this calls await q.return() + } + } + + expect(msgs).toEqual(['a', 'b']) + + // buffer should have drained at end + expect(q.length).toBe(0) + }) + + it('supports multiple simultaneous calls to next', async () => { + const q = new PushQueue() + q.push('a', 'b') + const msgs = await Promise.all([ + q.next(), + q.next(), + ]).then((m) => m.map(({ value }) => value)) + await q.return() + + expect(msgs).toEqual(['a', 'b']) + + // buffer should have drained at end + expect(q.length).toBe(0) + }) + + it('handles throw', async () => { + const q = new PushQueue(['a', 'b']) + expect(q.length).toBe(2) + + const msgs = [] + setTimeout(() => { + q.throw(new Error('expected error')) + q.push('c') // should no-op + }) + + await expect(async () => { + for await (const msg of q) { + msgs.push(msg) + } + }).rejects.toThrow('expected error') + + await wait(10) // wait for maybe push + // push('c') shouldn't have worked + expect(q.length).toBe(0) + + expect(msgs).toEqual(['a', 'b']) + }) + + it('handles throw early', async () => { + const q = new PushQueue() + q.throw(new Error('expected error')) + q.push('c') // should no-op + + const msgs = [] + await expect(async () => { + for await (const msg of q) { + msgs.push(msg) + } + }).rejects.toThrow('expected error') + + await wait(10) // wait for maybe push + // push('c') shouldn't have worked + expect(q.length).toBe(0) + + expect(msgs).toEqual([]) + }) + + describe('abort', () => { + it('can be aborted', async () => { + const ac = new AbortController() + + const q = new PushQueue(['a', 'b'], { + signal: ac.signal, + }) + + setTimeout(() => { + ac.abort() + q.push('nope1') // should no-op + }) + + const msgs = [] + await expect(async () => { + for await (const msg of q) { + msgs.push(msg) + } + }).rejects.toThrow('abort') + + expect(msgs).toEqual(['a', 'b']) + }) + + it('handles aborting multiple buffers', async () => { + const ac = new AbortController() + + async function create(items = ['a', 'b']) { + const q = new PushQueue(items, { + signal: ac.signal, + }) + const msgs = [] + await expect(async () => { + for await (const msg of q) { + msgs.push(msg) + } + }).rejects.toThrow('abort') + await wait(10) // wait for maybe push + expect(q.length).toBe(0) + expect(msgs).toEqual(items) + } + + setTimeout(() => { + ac.abort() + }) + + await Promise.all([ + create(['a', 'b']), + create(['c', 'd']), + create([]), + ]) + }) + + it('can abort before iteration', async () => { + const ac = new AbortController() + + const q = new PushQueue(['a', 'b'], { + signal: ac.signal, + }) + + ac.abort() + q.push('nope1') // should no-op + const msgs = [] + await expect(async () => { + for await (const msg of q) { + msgs.push(msg) + } + }).rejects.toThrow('abort') + expect(msgs).toEqual(['a', 'b']) // still gives buffered items + }) + + it('can abort before creating PushQueue', async () => { + const ac = new AbortController() + ac.abort() + + const q = new PushQueue(['a', 'b'], { + signal: ac.signal, + }) + q.push('nope1') // should no-op + + const msgs = [] + await expect(async () => { + for await (const msg of q) { + msgs.push(msg) + } + }).rejects.toThrow('abort') + expect(msgs).toEqual(['a', 'b']) // still gives buffered items + }) + }) +}) From bc41b4e299161c7ec5ab689855a64d495b1e9ac6 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 25 Sep 2020 12:10:28 -0400 Subject: [PATCH 068/517] Alternative next implementation for PushQueue async iterator. --- src/PushQueue.js | 106 +++++++++++++++++++++--------------- test/unit/PushQueue.test.js | 3 +- 2 files changed, 63 insertions(+), 46 deletions(-) diff --git a/src/PushQueue.js b/src/PushQueue.js index 5b5116cf1..5fc4e71ff 100644 --- a/src/PushQueue.js +++ b/src/PushQueue.js @@ -5,7 +5,7 @@ function createIterResult(value, done) { } } -class AbortError extends Error { +export class AbortError extends Error { constructor(msg = '', ...args) { super(`The operation was aborted. ${msg}`, ...args) if (Error.captureStackTrace) { @@ -18,9 +18,8 @@ class AbortError extends Error { * Async Iterable PushQueue * On throw/abort any items in buffer will be flushed before iteration throws. * Heavily based on (upcoming) events.on API in node: - * @see https://github.com/nodejs/node/blob/e36ffb72bebae55091304da51837ca204367dc16/lib/events.js#L707-L826 + * https://github.com/nodejs/node/blob/e36ffb72bebae55091304da51837ca204367dc16/lib/events.js#L707-L826 * - * ```js * const queue = new PushQueue([item1], { * signal: abortController.signal // optional * }) @@ -31,6 +30,7 @@ class AbortError extends Error { * queue.throw(err) // force the queue to throw * abortController.abort() // alternative * }) + * * try { * for await (const m of queue) { * console.log(m) @@ -41,7 +41,7 @@ class AbortError extends Error { * } finally { * // run clean up after iteration success/error * } - * ``` + * */ export default class PushQueue { @@ -65,53 +65,21 @@ export default class PushQueue { once: true }) } + + this.iterator = this.iterate() } onAbort() { return this.throw(new AbortError()) } - async next() { - // always feed from buffer first - if (this.buffer.length) { - return createIterResult(this.buffer.shift()) - } - - // handle queued error - if (this.error) { - const err = this.error - this.error = null - throw err - } - - // done - if (this.finished) { - return createIterResult(undefined, true) - } - - // wait for next push - return new Promise((resolve, reject) => { - this.nextQueue.push({ - resolve, - reject, - }) - }) + async next(...args) { + return this.iterator.next(...args) } async return() { this.finished = true - if (this.signal) { - this.signal.removeEventListener('abort', this.onAbort, { - once: true, - }) - } - - // clean up outstanding promises - for (const p of this.nextQueue) { - p.resolve(createIterResult(undefined, true)) - } - - return createIterResult(undefined, true) + await this._cleanup() } async throw(err) { @@ -131,6 +99,13 @@ export default class PushQueue { return this.buffer.length } + _cleanup() { + this.finished = true + for (const p of this.nextQueue) { + p.resolve() + } + } + push(...values) { if (this.finished) { // do nothing if done @@ -140,16 +115,59 @@ export default class PushQueue { const p = this.nextQueue.shift() if (p) { const [first, ...rest] = values - p.resolve(createIterResult(first)) + p.resolve(first) this.buffer.push(...rest) } else { this.buffer.push(...values) } } - [Symbol.asyncIterator]() { + async* iterate() { + while (true) { + // always feed from buffer first + while (this.buffer.length) { + yield this.buffer.shift() + } + + // handle queued error + if (this.error) { + const err = this.error + this.error = null + throw err + } + + // done + if (this.finished) { + return + } + // eslint-disable-next-line no-await-in-loop + const value = await new Promise((resolve, reject) => { + // wait for next push + this.nextQueue.push({ + resolve, + reject, + }) + }) + // ignore value if finished + if (this.finished) { + return + } + yield value + } + } + + async* [Symbol.asyncIterator]() { // NOTE: consider throwing if trying to iterate after finished // or maybe returning a new iterator? - return this + + try { + yield* this.iterator + } finally { + if (this.signal) { + this.signal.removeEventListener('abort', this.onAbort, { + once: true, + }) + } + } } } diff --git a/test/unit/PushQueue.test.js b/test/unit/PushQueue.test.js index fc96efdff..279853d57 100644 --- a/test/unit/PushQueue.test.js +++ b/test/unit/PushQueue.test.js @@ -134,9 +134,8 @@ describe('PushQueue', () => { await wait(10) // wait for maybe push // push('c') shouldn't have worked - expect(q.length).toBe(0) - expect(msgs).toEqual(['a', 'b']) + expect(q.length).toBe(0) }) it('handles throw early', async () => { From 29f4ed82c7e958535b617a7a82280d1012ab8e10 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 25 Sep 2020 16:43:30 -0400 Subject: [PATCH 069/517] Add Subscriptions. --- src/Stream.js | 275 ++++++++++++++++++++++++ test/integration/Stream.test.js | 356 ++++++++++++++++++++++++++++++++ 2 files changed, 631 insertions(+) create mode 100644 src/Stream.js create mode 100644 test/integration/Stream.test.js diff --git a/src/Stream.js b/src/Stream.js new file mode 100644 index 000000000..81adbe06f --- /dev/null +++ b/src/Stream.js @@ -0,0 +1,275 @@ +import pMemoize from 'p-memoize' +import pLimit from 'p-limit' +import { ControlLayer } from 'streamr-client-protocol' +import AbortController from 'node-abort-controller' + +import PushQueue, { AbortError } from './PushQueue' +import { uuid } from './utils' + +const { SubscribeRequest, UnsubscribeRequest, ControlMessage } = ControlLayer + +function validateOptions(optionsOrStreamId) { + if (!optionsOrStreamId) { + throw new Error('options is required!') + } + + // Backwards compatibility for giving a streamId as first argument + let options + if (typeof optionsOrStreamId === 'string') { + options = { + streamId: optionsOrStreamId, + streamPartition: 0, + } + } else if (typeof optionsOrStreamId === 'object') { + // shallow copy + options = { + streamPartition: 0, + ...optionsOrStreamId + } + } else { + throw new Error(`options must be an object! Given: ${optionsOrStreamId}`) + } + + if (!options.streamId) { + throw new Error(`streamId must be set, given: ${optionsOrStreamId}`) + } + + return options +} + +const PAIRS = new Map([ + [ControlMessage.TYPES.SubscribeRequest, ControlMessage.TYPES.SubscribeResponse], + [ControlMessage.TYPES.UnsubscribeRequest, ControlMessage.TYPES.UnsubscribeResponse], +]) + +async function waitForResponse({ connection, type, requestId }) { + return new Promise((resolve, reject) => { + let onErrorResponse + const onResponse = (res) => { + if (res.requestId !== requestId) { return } + // clean up err handler + connection.off(ControlMessage.TYPES.ErrorResponse, onErrorResponse) + resolve(res) + } + onErrorResponse = (res) => { + if (res.requestId !== requestId) { return } + // clean up success handler + connection.off(type, onResponse) + const error = new Error(res.errorMessage) + error.code = res.errorCode + reject(error) + } + connection.on(type, onResponse) + connection.on(ControlMessage.TYPES.ErrorResponse, onErrorResponse) + }) +} + +async function waitForRequestResponse(client, request) { + return waitForResponse({ + connection: client.connection, + type: PAIRS.get(request.type), + requestId: request.requestId, + }) +} + +function getIsMatchingStreamMessage({ streamId, streamPartition = 0 }) { + return function isMatchingStreamMessage({ streamMessage }) { + const msgStreamId = streamMessage.getStreamId() + if (streamId !== msgStreamId) { return false } + const msgPartition = streamMessage.getStreamPartition() + if (streamPartition !== msgPartition) { return false } + return true + } +} + +async function subscribe(client, { streamId, streamPartition = 0 }) { + const sessionToken = await client.session.getSessionToken() + const request = new SubscribeRequest({ + streamId, + streamPartition, + sessionToken, + requestId: uuid('sub'), + }) + + const onResponse = waitForRequestResponse(client, request) + + await client.send(request) + return onResponse +} + +async function unsubscribe(client, { streamId, streamPartition = 0 }) { + const sessionToken = await client.session.getSessionToken() + const request = new UnsubscribeRequest({ + streamId, + streamPartition, + sessionToken, + requestId: uuid('unsub'), + }) + + const onResponse = waitForRequestResponse(client, request) + + await client.send(request) + return onResponse +} + +async function* messageIterator(client, { streamId, streamPartition, signal }) { + let onMessage + try { + const queue = new PushQueue([], { + signal, + }) + const isMatchingStreamMessage = getIsMatchingStreamMessage({ + streamId, + streamPartition + }) + onMessage = (msg) => { + if (isMatchingStreamMessage(msg)) { + queue.push(msg) + } + } + client.connection.on(ControlMessage.TYPES.BroadcastMessage, onMessage) + yield* queue + } finally { + // clean up + client.connection.off(ControlMessage.TYPES.BroadcastMessage, onMessage) + } +} + +function SubKey({ streamId, streamPartition = 0 }) { + if (streamId == null) { throw new Error(`SubKey: invalid streamId: ${streamId} ${streamPartition}`) } + return `${streamId}|${streamPartition}` +} + +function Iterator(iterator, onFinally) { + return (async function* It() { + try { + yield* iterator + } finally { + await onFinally(iterator) + } + }()) +} + +class Subscription { + constructor(client, options) { + this.client = client + this.options = validateOptions(options) + this.key = SubKey(this.options) + this.abortController = new AbortController() + this.iterators = new Set() + this.queue = pLimit(1) + const sub = this.subscribe.bind(this) + const unsub = this.unsubscribe.bind(this) + this.subscribe = () => this.queue(sub) + this.unsubscribe = () => this.queue(unsub) + this.return = this.return.bind(this) + this._subscribe = pMemoize(() => { + return subscribe(this.client, this.options) + }) + this._unsubscribe = pMemoize(() => { + return unsubscribe(this.client, this.options) + }) + } + + async abort() { + this.abortController.abort() + await this.queue(() => {}) // pending tasks done + } + + async subscribe() { + this.shouldSubscribe = true + pMemoize.clear(this._unsubscribe) + await this._subscribe() + return this.iterate() + } + + async unsubscribe() { + this.shouldSubscribe = false + pMemoize.clear(this._subscribe) + await this._unsubscribe() + } + + async return() { + this.shouldSubscribe = false + await Promise.all([...this.iterators].map(async (it) => { + await it.return() + await this.cleanup(it) + })) + } + + async cleanup(it) { + // if iterator never started need to manually clean it + this.iterators.delete(it) + if (!this.iterators.size) { + // unsubscribe if no more iterators + await this.unsubscribe() + } + } + + count() { + return this.iterators.size + } + + async* createIterator() { + if (!this.shouldSubscribe) { + return + } + + yield* messageIterator(this.client, { + signal: this.abortController.signal, + ...this.options, + }) + } + + iterate() { + const it = Iterator(this.createIterator(), async () => { + await this.cleanup(it) + }) + this.iterators.add(it) + return it + } + + [Symbol.asyncIterator]() { + return this.iterate() + } +} + +export default class Subscriptions { + constructor(client) { + this.client = client + this.subscriptions = new Map() + } + + get(options) { + const key = SubKey(validateOptions(options)) + return this.subscriptions.get(key) + } + + abort(options) { + const sub = this.get(options) + return sub && sub.abort() + } + + count(options) { + const sub = this.get(options) + return sub ? sub.count() : -1 + } + + async subscribe(options) { + const key = SubKey(validateOptions(options)) + const sub = ( + this.subscriptions.get(key) + || this.subscriptions.set(key, new Subscription(this.client, options)).get(key) + ) + + return sub.subscribe() + } + + async unsubscribe(options) { + const key = SubKey(validateOptions(options)) + const sub = this.subscriptions.get(key) + if (!sub) { return } + await sub.queue(() => {}) // wait for any outstanding operations + await sub.return() // close all iterators (thus unsubscribe) + } +} diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js new file mode 100644 index 000000000..f75745284 --- /dev/null +++ b/test/integration/Stream.test.js @@ -0,0 +1,356 @@ +import { ControlLayer } from 'streamr-client-protocol' +import { wait } from 'streamr-test-utils' + +import { uid, fakePrivateKey } from '../utils' +import StreamrClient from '../../src' +import Connection from '../../src/Connection' +import MessageStream from '../../src/Stream' + +import config from './config' + +const { ControlMessage } = ControlLayer + +const Msg = (opts) => ({ + value: uid('msg'), + ...opts, +}) + +describe('StreamrClient Stream', () => { + let expectErrors = 0 // check no errors by default + let onError = jest.fn() + let client + let stream + + const createClient = (opts = {}) => { + const c = new StreamrClient({ + auth: { + privateKey: fakePrivateKey(), + }, + autoConnect: false, + autoDisconnect: false, + maxRetries: 2, + ...config.clientOptions, + ...opts, + }) + c.onError = jest.fn() + c.on('error', onError) + return c + } + + beforeEach(async () => { + expectErrors = 0 + onError = jest.fn() + client = createClient() + + stream = await client.createStream({ + name: uid('stream') + }) + await client.connect() + }) + + afterEach(async () => { + await wait() + // ensure no unexpected errors + expect(onError).toHaveBeenCalledTimes(expectErrors) + if (client) { + expect(client.onError).toHaveBeenCalledTimes(expectErrors) + } + }) + + afterEach(async () => { + await wait() + if (client) { + client.debug('disconnecting after test') + await client.disconnect() + } + + const openSockets = Connection.getOpen() + if (openSockets !== 0) { + throw new Error(`sockets not closed: ${openSockets}`) + } + }) + + it('can subscribe to stream and get updates then auto unsubscribe', async () => { + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) + + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + + const received = [] + for await (const m of sub) { + received.push(m) + if (received.length === published.length) { + sub.return() + } + } + + expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe to stream multiple times, get updates then unsubscribe', async () => { + const M = new MessageStream(client) + const sub1 = await M.subscribe(stream.id) + const sub2 = await M.subscribe(stream.id) + + expect(M.count(stream.id)).toBe(2) + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + + async function subscribe(s) { + const received = [] + for await (const m of s) { + received.push(m) + if (received.length === published.length) { + s.return() + } + } + return received + } + + const [received1, received2] = await Promise.all([ + subscribe(sub1), + subscribe(sub2), + ]) + + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(received2).toEqual(received1) + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe to stream multiple times in parallel, get updates then unsubscribe', async () => { + const M = new MessageStream(client) + const [sub1, sub2] = await Promise.all([ + M.subscribe(stream.id), + M.subscribe(stream.id), + ]) + + expect(M.count(stream.id)).toBe(2) + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + + async function subscribe(s) { + const received = [] + for await (const m of s) { + received.push(m) + if (received.length === published.length) { + s.return() + } + } + return received + } + + const [received1, received2] = await Promise.all([ + subscribe(sub1), + subscribe(sub2), + ]) + + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(received2).toEqual(received1) + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe to stream and get some updates then unsubscribe mid-stream', async () => { + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) + + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + + const received = [] + for await (const m of sub) { + received.push(m) + if (received.length === 1) { + await M.unsubscribe(stream.id) + } + } + + expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(M.count(stream.id)).toBe(0) + }) + + it('finishes unsubscribe before returning', async () => { + const msgEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + msgEvents.push(m) + }) + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) + + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + + const received = [] + for await (const m of sub) { + received.push(m) + if (received.length === 1) { + await sub.return() + expect(msgEvents).toHaveLength(1) + } + } + + expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe to stream multiple times then unsubscribe mid-stream', async () => { + const M = new MessageStream(client) + const sub1 = await M.subscribe(stream.id) + const sub2 = await M.subscribe(stream.id) + + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + + async function subscribe(s, doUnsub) { + const received = [] + for await (const m of s) { + received.push(m) + if (doUnsub === received.length) { + await M.unsubscribe(stream.id) + } + } + return received + } + + const [received1, received2] = await Promise.all([ + subscribe(sub1), + subscribe(sub2, 2), + ]) + + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 3)) + expect(received2.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 2)) + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe to stream multiple times then abort mid-stream', async () => { + const M = new MessageStream(client) + const sub1 = await M.subscribe(stream.id) + const sub2 = await M.subscribe(stream.id) + + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + + async function subscribe(s, doUnsub) { + const received = [] + for await (const m of s) { + received.push(m) + if (doUnsub === received.length) { + await M.abort(stream.id) + } + } + return received + } + + await Promise.all([ + expect(() => subscribe(sub1)).rejects.toThrow('abort'), + expect(() => subscribe(sub2, 2)).rejects.toThrow('abort'), + ]) + + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe to stream multiple times then return mid-stream', async () => { + const M = new MessageStream(client) + const sub1 = await M.subscribe(stream.id) + const sub2 = await M.subscribe(stream.id) + + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + + async function subscribe(s, doUnsub) { + const received = [] + for await (const m of s) { + received.push(m) + if (doUnsub === received.length) { + return received + } + } + return received + } + + const [received1, received2] = await Promise.all([ + subscribe(sub1, 1), + subscribe(sub2, 4), + ]) + + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(received2.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 4)) + + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe then unsubscribe then subscribe', async () => { + const msgEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + msgEvents.push(m) + }) + const M = new MessageStream(client) + const [sub] = await Promise.all([ + M.subscribe(stream.id), + M.unsubscribe(stream.id), + ]) + + const published = [] + for (let i = 0; i < 2; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + + const received = [] + + for await (const m of sub) { + received.push(m) + } + expect(msgEvents).toHaveLength(1) + + expect(received).toHaveLength(0) + expect(M.count(stream.id)).toBe(0) + }) +}) From 5799699f8b98070ef2536a1397b63da4fef907bd Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Sat, 26 Sep 2020 15:58:24 -0400 Subject: [PATCH 070/517] Improve unsubscribe/subscribe sequencing. --- src/Stream.js | 80 ++++++++++++++++++++------------- test/integration/Stream.test.js | 55 ++++++++++++++++++----- 2 files changed, 91 insertions(+), 44 deletions(-) diff --git a/src/Stream.js b/src/Stream.js index 81adbe06f..0e0b3ac72 100644 --- a/src/Stream.js +++ b/src/Stream.js @@ -140,35 +140,55 @@ function SubKey({ streamId, streamPartition = 0 }) { return `${streamId}|${streamPartition}` } -function Iterator(iterator, onFinally) { - return (async function* It() { +/** + * Allows injecting a function to execute after an iterator finishes. + * Executes finally function even if generator not started. + */ + +function iteratorFinally(iterator, onFinally) { + let started = false + const g = (async function* It() { + started = true try { yield* iterator } finally { await onFinally(iterator) } }()) + + // overrides return/throw to call onFinally even if generator was never started + const oldReturn = g.return + g.return = async (...args) => { + if (!started) { + await onFinally(iterator) + } + return oldReturn.call(g, ...args) + } + const oldThrow = g.throw + g.throw = async (...args) => { + if (!started) { + await onFinally(iterator) + } + return oldThrow.call(g, ...args) + } + return g } class Subscription { constructor(client, options) { this.client = client this.options = validateOptions(options) - this.key = SubKey(this.options) this.abortController = new AbortController() this.iterators = new Set() + this.queue = pLimit(1) const sub = this.subscribe.bind(this) const unsub = this.unsubscribe.bind(this) this.subscribe = () => this.queue(sub) this.unsubscribe = () => this.queue(unsub) this.return = this.return.bind(this) - this._subscribe = pMemoize(() => { - return subscribe(this.client, this.options) - }) - this._unsubscribe = pMemoize(() => { - return unsubscribe(this.client, this.options) - }) + this.sendSubscribe = pMemoize(this.sendSubscribe.bind(this)) + this.sendUnsubscribe = pMemoize(this.sendUnsubscribe.bind(this)) } async abort() { @@ -176,29 +196,33 @@ class Subscription { await this.queue(() => {}) // pending tasks done } + async sendSubscribe() { + return subscribe(this.client, this.options) + } + + async sendUnsubscribe() { + return unsubscribe(this.client, this.options) + } + async subscribe() { - this.shouldSubscribe = true - pMemoize.clear(this._unsubscribe) - await this._subscribe() + pMemoize.clear(this.sendUnsubscribe) + await this.sendSubscribe() return this.iterate() } async unsubscribe() { - this.shouldSubscribe = false - pMemoize.clear(this._subscribe) - await this._unsubscribe() + pMemoize.clear(this.sendSubscribe) + await this.sendUnsubscribe() } async return() { - this.shouldSubscribe = false await Promise.all([...this.iterators].map(async (it) => { await it.return() - await this.cleanup(it) })) } - async cleanup(it) { - // if iterator never started need to manually clean it + async _cleanup(it) { + // if iterator never started, finally block never called, thus need to manually clean it this.iterators.delete(it) if (!this.iterators.size) { // unsubscribe if no more iterators @@ -210,21 +234,13 @@ class Subscription { return this.iterators.size } - async* createIterator() { - if (!this.shouldSubscribe) { - return - } - - yield* messageIterator(this.client, { + iterate() { + const it = iteratorFinally(messageIterator(this.client, { signal: this.abortController.signal, ...this.options, - }) - } - - iterate() { - const it = Iterator(this.createIterator(), async () => { - await this.cleanup(it) - }) + }), async () => ( + this._cleanup(it) + )) this.iterators.add(it) return it } diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index f75745284..a4374db0b 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -193,13 +193,13 @@ describe('StreamrClient Stream', () => { }) it('finishes unsubscribe before returning', async () => { - const msgEvents = [] + const unsubscribeEvents = [] client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - msgEvents.push(m) + unsubscribeEvents.push(m) }) + const M = new MessageStream(client) const sub = await M.subscribe(stream.id) - expect(M.count(stream.id)).toBe(1) const published = [] for (let i = 0; i < 5; i++) { @@ -212,13 +212,14 @@ describe('StreamrClient Stream', () => { const received = [] for await (const m of sub) { received.push(m) - if (received.length === 1) { + if (received.length === 2) { + expect(unsubscribeEvents).toHaveLength(0) await sub.return() - expect(msgEvents).toHaveLength(1) + expect(unsubscribeEvents).toHaveLength(1) + expect(M.count(stream.id)).toBe(0) } } - - expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(received).toHaveLength(2) expect(M.count(stream.id)).toBe(0) }) @@ -324,10 +325,40 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) }) - it('can subscribe then unsubscribe then subscribe', async () => { - const msgEvents = [] + it('will clean up if iterator returned before start', async () => { + const unsubscribeEvents = [] client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - msgEvents.push(m) + unsubscribeEvents.push(m) + }) + + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) + await sub.return() + expect(M.count(stream.id)).toBe(0) + + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + + const received = [] + for await (const m of sub) { + received.push(m) + } + expect(received).toHaveLength(0) + expect(unsubscribeEvents).toHaveLength(1) + + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe then unsubscribe in parallel', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) }) const M = new MessageStream(client) const [sub] = await Promise.all([ @@ -344,13 +375,13 @@ describe('StreamrClient Stream', () => { } const received = [] - for await (const m of sub) { received.push(m) } - expect(msgEvents).toHaveLength(1) + // shouldn't get any messages expect(received).toHaveLength(0) + expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) }) }) From 9084f46716b490ee99ca406084fb6054041ddbe5 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 28 Sep 2020 15:05:52 -0400 Subject: [PATCH 071/517] PushQueue finish/error before handling buffered data. --- src/PushQueue.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/PushQueue.js b/src/PushQueue.js index 5fc4e71ff..c8add4773 100644 --- a/src/PushQueue.js +++ b/src/PushQueue.js @@ -124,9 +124,14 @@ export default class PushQueue { async* iterate() { while (true) { - // always feed from buffer first - while (this.buffer.length) { - yield this.buffer.shift() + // feed from buffer first + const buffer = this.buffer.slice() + this.buffer.length = 0 // prevent endless loop + while (buffer.length) { + if (this.error || this.finished) { + break + } + yield buffer.shift() } // handle queued error From 3e20f1ca85842bfe1f4a10b9581b46c5aa8361ef Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 28 Sep 2020 15:07:31 -0400 Subject: [PATCH 072/517] Attach iteration listeners immediately. Update tests to repeat. --- src/Stream.js | 63 ++-- test/integration/Stream.test.js | 572 ++++++++++++++++---------------- 2 files changed, 314 insertions(+), 321 deletions(-) diff --git a/src/Stream.js b/src/Stream.js index 0e0b3ac72..383fc4c4e 100644 --- a/src/Stream.js +++ b/src/Stream.js @@ -112,34 +112,6 @@ async function unsubscribe(client, { streamId, streamPartition = 0 }) { return onResponse } -async function* messageIterator(client, { streamId, streamPartition, signal }) { - let onMessage - try { - const queue = new PushQueue([], { - signal, - }) - const isMatchingStreamMessage = getIsMatchingStreamMessage({ - streamId, - streamPartition - }) - onMessage = (msg) => { - if (isMatchingStreamMessage(msg)) { - queue.push(msg) - } - } - client.connection.on(ControlMessage.TYPES.BroadcastMessage, onMessage) - yield* queue - } finally { - // clean up - client.connection.off(ControlMessage.TYPES.BroadcastMessage, onMessage) - } -} - -function SubKey({ streamId, streamPartition = 0 }) { - if (streamId == null) { throw new Error(`SubKey: invalid streamId: ${streamId} ${streamPartition}`) } - return `${streamId}|${streamPartition}` -} - /** * Allows injecting a function to execute after an iterator finishes. * Executes finally function even if generator not started. @@ -174,6 +146,30 @@ function iteratorFinally(iterator, onFinally) { return g } +function messageIterator(client, { streamId, streamPartition, signal }) { + const queue = new PushQueue([], { + signal, + }) + const isMatchingStreamMessage = getIsMatchingStreamMessage({ + streamId, + streamPartition + }) + const onMessage = (msg) => { + if (isMatchingStreamMessage(msg)) { + queue.push(msg) + } + } + client.connection.on(ControlMessage.TYPES.BroadcastMessage, onMessage) + return iteratorFinally(queue, () => { + client.connection.off(ControlMessage.TYPES.BroadcastMessage, onMessage) + }) +} + +function SubKey({ streamId, streamPartition = 0 }) { + if (streamId == null) { throw new Error(`SubKey: invalid streamId: ${streamId} ${streamPartition}`) } + return `${streamId}|${streamPartition}` +} + class Subscription { constructor(client, options) { this.client = client @@ -191,6 +187,10 @@ class Subscription { this.sendUnsubscribe = pMemoize(this.sendUnsubscribe.bind(this)) } + hasPending() { + return !!(this.queue.activeCount || this.queue.pendingCount) + } + async abort() { this.abortController.abort() await this.queue(() => {}) // pending tasks done @@ -285,7 +285,12 @@ export default class Subscriptions { const key = SubKey(validateOptions(options)) const sub = this.subscriptions.get(key) if (!sub) { return } - await sub.queue(() => {}) // wait for any outstanding operations + + // wait for any outstanding operations + if (sub.hasPending()) { + await sub.queue(() => {}) + } + await sub.return() // close all iterators (thus unsubscribe) } } diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index a4374db0b..3e1498c39 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -15,6 +15,20 @@ const Msg = (opts) => ({ ...opts, }) +async function collect(iterator, fn = () => {}) { + const received = [] + for await (const msg of iterator) { + received.push(msg) + await fn({ + msg, iterator, received, + }) + } + + return received +} + +const TEST_REPEATS = 5 + describe('StreamrClient Stream', () => { let expectErrors = 0 // check no errors by default let onError = jest.fn() @@ -38,14 +52,14 @@ describe('StreamrClient Stream', () => { } beforeEach(async () => { + client = createClient() + await client.connect() + console.log = client.debug expectErrors = 0 onError = jest.fn() - client = createClient() - stream = await client.createStream({ name: uid('stream') }) - await client.connect() }) afterEach(async () => { @@ -70,318 +84,292 @@ describe('StreamrClient Stream', () => { } }) - it('can subscribe to stream and get updates then auto unsubscribe', async () => { + it('attaches listener at subscribe time', async () => { const M = new MessageStream(client) + const beforeCount = client.connection.listenerCount(ControlMessage.TYPES.BroadcastMessage) const sub = await M.subscribe(stream.id) + const afterCount = client.connection.listenerCount(ControlMessage.TYPES.BroadcastMessage) + expect(afterCount).toBeGreaterThan(beforeCount) expect(M.count(stream.id)).toBe(1) - - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } - - const received = [] - for await (const m of sub) { - received.push(m) - if (received.length === published.length) { - sub.return() - } - } - - expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + await sub.return() expect(M.count(stream.id)).toBe(0) }) - it('can subscribe to stream multiple times, get updates then unsubscribe', async () => { - const M = new MessageStream(client) - const sub1 = await M.subscribe(stream.id) - const sub2 = await M.subscribe(stream.id) - - expect(M.count(stream.id)).toBe(2) - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } - - async function subscribe(s) { - const received = [] - for await (const m of s) { - received.push(m) - if (received.length === published.length) { - s.return() + for (let k = 0; k < TEST_REPEATS; k++) { + // eslint-disable-next-line no-loop-func + describe(`test repeat ${k + 1} of ${TEST_REPEATS}`, () => { + it('can subscribe to stream and get updates then auto unsubscribe', async () => { + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) + + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) } - } - return received - } - - const [received1, received2] = await Promise.all([ - subscribe(sub1), - subscribe(sub2), - ]) - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) - expect(received2).toEqual(received1) - expect(M.count(stream.id)).toBe(0) - }) - - it('can subscribe to stream multiple times in parallel, get updates then unsubscribe', async () => { - const M = new MessageStream(client) - const [sub1, sub2] = await Promise.all([ - M.subscribe(stream.id), - M.subscribe(stream.id), - ]) - - expect(M.count(stream.id)).toBe(2) - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } - - async function subscribe(s) { - const received = [] - for await (const m of s) { - received.push(m) - if (received.length === published.length) { - s.return() + const received = [] + for await (const m of sub) { + received.push(m) + if (received.length === published.length) { + await sub.return() + } + } + expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe to stream multiple times, get updates then unsubscribe', async () => { + const M = new MessageStream(client) + const sub1 = await M.subscribe(stream.id) + const sub2 = await M.subscribe(stream.id) + + expect(M.count(stream.id)).toBe(2) + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) } - } - return received - } - - const [received1, received2] = await Promise.all([ - subscribe(sub1), - subscribe(sub2), - ]) - - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) - expect(received2).toEqual(received1) - expect(M.count(stream.id)).toBe(0) - }) - - it('can subscribe to stream and get some updates then unsubscribe mid-stream', async () => { - const M = new MessageStream(client) - const sub = await M.subscribe(stream.id) - expect(M.count(stream.id)).toBe(1) - - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } - const received = [] - for await (const m of sub) { - received.push(m) - if (received.length === 1) { - await M.unsubscribe(stream.id) - } - } + const [received1, received2] = await Promise.all([ + collect(sub1, ({ received, iterator }) => { + if (received.length === published.length) { + iterator.return() + } + }), + collect(sub2, ({ received, iterator }) => { + if (received.length === published.length) { + iterator.return() + } + }), + ]) + + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(received2).toEqual(received1) + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe to stream multiple times in parallel, get updates then unsubscribe', async () => { + const M = new MessageStream(client) + const [sub1, sub2] = await Promise.all([ + M.subscribe(stream.id), + M.subscribe(stream.id), + ]) + + expect(M.count(stream.id)).toBe(2) + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } - expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) - expect(M.count(stream.id)).toBe(0) - }) + const [received1, received2] = await Promise.all([ + collect(sub1, ({ received, iterator }) => { + if (received.length === published.length) { + iterator.return() + } + }), + collect(sub2, ({ received, iterator }) => { + if (received.length === published.length) { + iterator.return() + } + }) + ]) + + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(received2).toEqual(received1) + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe to stream and get some updates then unsubscribe mid-stream', async () => { + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) + + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } - it('finishes unsubscribe before returning', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) + const receivedMsgs = await collect(sub, ({ received, iterator }) => { + if (received.length === 1) { + iterator.return() + } + }) - const M = new MessageStream(client) - const sub = await M.subscribe(stream.id) + expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(M.count(stream.id)).toBe(0) + }) + + it('finishes unsubscribe before returning', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) + + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + const receivedMsgs = await collect(sub, async ({ received }) => { + if (received.length === 2) { + expect(unsubscribeEvents).toHaveLength(0) + await sub.return() + expect(unsubscribeEvents).toHaveLength(1) + expect(M.count(stream.id)).toBe(0) + } + }) - const received = [] - for await (const m of sub) { - received.push(m) - if (received.length === 2) { - expect(unsubscribeEvents).toHaveLength(0) + expect(receivedMsgs).toHaveLength(2) + expect(M.count(stream.id)).toBe(0) + }) + + describe('mid-stream stop methods', () => { + let sub1 + let sub2 + let M + let published + + beforeEach(async () => { + M = new MessageStream(client) + sub1 = await M.subscribe(stream.id) + sub2 = await M.subscribe(stream.id) + + published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + // eslint-disable-next-line no-await-in-loop + await wait(100) + } + }) + + it('can subscribe to stream multiple times then unsubscribe mid-stream', async () => { + const [received1, received2] = await Promise.all([ + collect(sub1), + collect(sub2, async ({ received }) => { + if (received.length === 1) { + await M.unsubscribe(stream.id) + } + }), + ]) + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(received2.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe to stream multiple times then abort mid-stream', async () => { + let received1 + let received2 + await Promise.all([ + expect(() => collect(sub1, ({ received }) => { + received1 = received + })).rejects.toThrow('abort'), + expect(() => collect(sub2, async ({ received }) => { + received2 = received + if (received.length === 1) { + await M.abort(stream.id) + } + })).rejects.toThrow('abort'), + ]) + + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(received2.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe to stream multiple times then return mid-stream', async () => { + const [received1, received2] = await Promise.all([ + collect(sub1, async ({ received, iterator }) => { + if (received.length === 4) { + await iterator.return() + } + }), + collect(sub2, async ({ received, iterator }) => { + if (received.length === 1) { + await iterator.return() + } + }) + ]) + + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 4)) + expect(received2.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + + expect(M.count(stream.id)).toBe(0) + }) + }) + + it('will clean up if iterator returned before start', async () => { + const M = new MessageStream(client) + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) + + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) await sub.return() - expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) - } - } - expect(received).toHaveLength(2) - expect(M.count(stream.id)).toBe(0) - }) - - it('can subscribe to stream multiple times then unsubscribe mid-stream', async () => { - const M = new MessageStream(client) - const sub1 = await M.subscribe(stream.id) - const sub2 = await M.subscribe(stream.id) - - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } - - async function subscribe(s, doUnsub) { - const received = [] - for await (const m of s) { - received.push(m) - if (doUnsub === received.length) { - await M.unsubscribe(stream.id) + const received = [] + for await (const m of sub) { + received.push(m) } - } - return received - } - - const [received1, received2] = await Promise.all([ - subscribe(sub1), - subscribe(sub2, 2), - ]) - - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 3)) - expect(received2.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 2)) - expect(M.count(stream.id)).toBe(0) - }) - - it('can subscribe to stream multiple times then abort mid-stream', async () => { - const M = new MessageStream(client) - const sub1 = await M.subscribe(stream.id) - const sub2 = await M.subscribe(stream.id) - - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + expect(received).toHaveLength(0) + expect(unsubscribeEvents).toHaveLength(1) - async function subscribe(s, doUnsub) { - const received = [] - for await (const m of s) { - received.push(m) - if (doUnsub === received.length) { - await M.abort(stream.id) + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe then unsubscribe in parallel', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) + const M = new MessageStream(client) + const [sub] = await Promise.all([ + M.subscribe(stream.id), + M.unsubscribe(stream.id), + ]) + + const published = [] + for (let i = 0; i < 2; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) } - } - return received - } - - await Promise.all([ - expect(() => subscribe(sub1)).rejects.toThrow('abort'), - expect(() => subscribe(sub2, 2)).rejects.toThrow('abort'), - ]) - - expect(M.count(stream.id)).toBe(0) - }) - it('can subscribe to stream multiple times then return mid-stream', async () => { - const M = new MessageStream(client) - const sub1 = await M.subscribe(stream.id) - const sub2 = await M.subscribe(stream.id) - - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } - - async function subscribe(s, doUnsub) { - const received = [] - for await (const m of s) { - received.push(m) - if (doUnsub === received.length) { - return received + const received = [] + for await (const m of sub) { + received.push(m) } - } - return received - } - const [received1, received2] = await Promise.all([ - subscribe(sub1, 1), - subscribe(sub2, 4), - ]) - - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) - expect(received2.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 4)) - - expect(M.count(stream.id)).toBe(0) - }) - - it('will clean up if iterator returned before start', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) - - const M = new MessageStream(client) - const sub = await M.subscribe(stream.id) - expect(M.count(stream.id)).toBe(1) - await sub.return() - expect(M.count(stream.id)).toBe(0) - - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } - - const received = [] - for await (const m of sub) { - received.push(m) - } - expect(received).toHaveLength(0) - expect(unsubscribeEvents).toHaveLength(1) - - expect(M.count(stream.id)).toBe(0) - }) - - it('can subscribe then unsubscribe in parallel', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) + // shouldn't get any messages + expect(received).toHaveLength(0) + expect(unsubscribeEvents).toHaveLength(1) + expect(M.count(stream.id)).toBe(0) + }) }) - const M = new MessageStream(client) - const [sub] = await Promise.all([ - M.subscribe(stream.id), - M.unsubscribe(stream.id), - ]) - - const published = [] - for (let i = 0; i < 2; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } - - const received = [] - for await (const m of sub) { - received.push(m) - } - - // shouldn't get any messages - expect(received).toHaveLength(0) - expect(unsubscribeEvents).toHaveLength(1) - expect(M.count(stream.id)).toBe(0) - }) + } }) From 4efeddf0dc8423c502c4b4eb9f3b2935da93eefd Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 28 Sep 2020 15:12:25 -0400 Subject: [PATCH 073/517] Just use streams. --- src/PushQueue.js | 178 -------------------------- src/Stream.js | 41 +++++- test/unit/PushQueue.test.js | 248 ------------------------------------ 3 files changed, 36 insertions(+), 431 deletions(-) delete mode 100644 src/PushQueue.js delete mode 100644 test/unit/PushQueue.test.js diff --git a/src/PushQueue.js b/src/PushQueue.js deleted file mode 100644 index c8add4773..000000000 --- a/src/PushQueue.js +++ /dev/null @@ -1,178 +0,0 @@ -function createIterResult(value, done) { - return { - value, - done: !!done, - } -} - -export class AbortError extends Error { - constructor(msg = '', ...args) { - super(`The operation was aborted. ${msg}`, ...args) - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor) - } - } -} - -/** - * Async Iterable PushQueue - * On throw/abort any items in buffer will be flushed before iteration throws. - * Heavily based on (upcoming) events.on API in node: - * https://github.com/nodejs/node/blob/e36ffb72bebae55091304da51837ca204367dc16/lib/events.js#L707-L826 - * - * const queue = new PushQueue([item1], { - * signal: abortController.signal // optional - * }) - * queue.push(item2, item3) // supports pushing multiple at once - * setTimeout(() => { - * queue.push(item4) // push asynchronously, iterator will wait - * queue.return() // need to explicitly end iteration or it will continue forever - * queue.throw(err) // force the queue to throw - * abortController.abort() // alternative - * }) - * - * try { - * for await (const m of queue) { - * console.log(m) - * break // this calls await queue.return() - * } - * } catch (err) { - * // queue threw an error - * } finally { - * // run clean up after iteration success/error - * } - * - */ - -export default class PushQueue { - constructor(items = [], { signal } = {}) { - this.buffer = [...items] - this.finished = false - this.error = null // queued error - this.nextQueue = [] // queued promises for next() - - this[Symbol.asyncIterator] = this[Symbol.asyncIterator].bind(this) - this.onAbort = this.onAbort.bind(this) - - // abort signal handling - this.signal = signal - if (signal) { - if (signal.aborted) { - this.onAbort() - } - - signal.addEventListener('abort', this.onAbort, { - once: true - }) - } - - this.iterator = this.iterate() - } - - onAbort() { - return this.throw(new AbortError()) - } - - async next(...args) { - return this.iterator.next(...args) - } - - async return() { - this.finished = true - await this._cleanup() - } - - async throw(err) { - this.finished = true - const p = this.nextQueue.shift() - if (p) { - p.reject(err) - } else { - // for next() - this.error = err - } - - return this.return() - } - - get length() { - return this.buffer.length - } - - _cleanup() { - this.finished = true - for (const p of this.nextQueue) { - p.resolve() - } - } - - push(...values) { - if (this.finished) { - // do nothing if done - return - } - - const p = this.nextQueue.shift() - if (p) { - const [first, ...rest] = values - p.resolve(first) - this.buffer.push(...rest) - } else { - this.buffer.push(...values) - } - } - - async* iterate() { - while (true) { - // feed from buffer first - const buffer = this.buffer.slice() - this.buffer.length = 0 // prevent endless loop - while (buffer.length) { - if (this.error || this.finished) { - break - } - yield buffer.shift() - } - - // handle queued error - if (this.error) { - const err = this.error - this.error = null - throw err - } - - // done - if (this.finished) { - return - } - // eslint-disable-next-line no-await-in-loop - const value = await new Promise((resolve, reject) => { - // wait for next push - this.nextQueue.push({ - resolve, - reject, - }) - }) - // ignore value if finished - if (this.finished) { - return - } - yield value - } - } - - async* [Symbol.asyncIterator]() { - // NOTE: consider throwing if trying to iterate after finished - // or maybe returning a new iterator? - - try { - yield* this.iterator - } finally { - if (this.signal) { - this.signal.removeEventListener('abort', this.onAbort, { - once: true, - }) - } - } - } -} diff --git a/src/Stream.js b/src/Stream.js index 383fc4c4e..b2b51f4f5 100644 --- a/src/Stream.js +++ b/src/Stream.js @@ -1,13 +1,23 @@ +import { PassThrough } from 'stream' + import pMemoize from 'p-memoize' import pLimit from 'p-limit' import { ControlLayer } from 'streamr-client-protocol' import AbortController from 'node-abort-controller' -import PushQueue, { AbortError } from './PushQueue' import { uuid } from './utils' const { SubscribeRequest, UnsubscribeRequest, ControlMessage } = ControlLayer +export class AbortError extends Error { + constructor(msg = '', ...args) { + super(`The operation was aborted. ${msg}`, ...args) + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor) + } + } +} + function validateOptions(optionsOrStreamId) { if (!optionsOrStreamId) { throw new Error('options is required!') @@ -147,22 +157,43 @@ function iteratorFinally(iterator, onFinally) { } function messageIterator(client, { streamId, streamPartition, signal }) { - const queue = new PushQueue([], { - signal, + if (signal.aborted) { + throw new AbortError() + } + + const queue = new PassThrough({ + objectMode: true, }) + + const onAbort = () => { + return queue.destroy(new AbortError()) + } + + signal.addEventListener('abort', onAbort, { + once: true + }) + const isMatchingStreamMessage = getIsMatchingStreamMessage({ streamId, streamPartition }) + const onMessage = (msg) => { if (isMatchingStreamMessage(msg)) { - queue.push(msg) + queue.write(msg) } } + client.connection.on(ControlMessage.TYPES.BroadcastMessage, onMessage) - return iteratorFinally(queue, () => { + queue.once('close', () => { client.connection.off(ControlMessage.TYPES.BroadcastMessage, onMessage) + if (signal) { + signal.removeEventListener('abort', onAbort, { + once: true, + }) + } }) + return queue } function SubKey({ streamId, streamPartition = 0 }) { diff --git a/test/unit/PushQueue.test.js b/test/unit/PushQueue.test.js deleted file mode 100644 index 279853d57..000000000 --- a/test/unit/PushQueue.test.js +++ /dev/null @@ -1,248 +0,0 @@ -import Debug from 'debug' -import { wait } from 'streamr-test-utils' -import AbortController from 'node-abort-controller' - -import PushQueue from '../../src/PushQueue' - -console.log = Debug('Streamr::console') - -describe('PushQueue', () => { - it('supports pre-buffering, async push & return', async () => { - const q = new PushQueue() - expect(q.length).toBe(0) - const items = [1, 2, 3, 4] - q.push(items[0]) - expect(q.length).toBe(1) - q.push(items[1]) - expect(q.length).toBe(2) - - setTimeout(() => { - // buffer should have drained by now - expect(q.length).toBe(0) - q.push(items[2]) - q.push(items[3]) - setTimeout(() => { - q.return(5) // both items above should get through - q.push('nope') // this should not - }, 20) - }, 10) - - let i = 0 - for await (const msg of q) { - expect(msg).toBe(items[i]) - i += 1 - } - - expect(i).toBe(4) - // buffer should have drained at end - expect(q.length).toBe(0) - }) - - it('supports passing initial values to constructor', async () => { - const q = new PushQueue(['a', 'b']) - expect(q.length).toBe(2) - - const msgs = [] - for await (const msg of q) { - msgs.push(msg) - if (!q.length) { - break // this calls await q.return() - } - } - - expect(msgs).toEqual(['a', 'b']) - - // buffer should have drained at end - expect(q.length).toBe(0) - - // these should have no effect - q.push('c') - await q.return() - }) - - it('consumes buffered items even after return', async () => { - const q = new PushQueue(['a', 'b']) - q.return() - const msgs = [] - for await (const msg of q) { - msgs.push(msg) - } - expect(msgs).toEqual(['a', 'b']) - }) - - it('handles iterating again', async () => { - const q = new PushQueue(['a', 'b']) - q.return() - const msgs = [] - for await (const msg of q) { - msgs.push(msg) - } - // can't iterate again after return - for await (const msg of q) { - throw new Error('should not get here ' + msg) - } - expect(msgs).toEqual(['a', 'b']) - }) - - it('supports passing multiple values to push', async () => { - const q = new PushQueue() - q.push('a', 'b') - const msgs = [] - for await (const msg of q) { - msgs.push(msg) - if (!q.length) { - break // this calls await q.return() - } - } - - expect(msgs).toEqual(['a', 'b']) - - // buffer should have drained at end - expect(q.length).toBe(0) - }) - - it('supports multiple simultaneous calls to next', async () => { - const q = new PushQueue() - q.push('a', 'b') - const msgs = await Promise.all([ - q.next(), - q.next(), - ]).then((m) => m.map(({ value }) => value)) - await q.return() - - expect(msgs).toEqual(['a', 'b']) - - // buffer should have drained at end - expect(q.length).toBe(0) - }) - - it('handles throw', async () => { - const q = new PushQueue(['a', 'b']) - expect(q.length).toBe(2) - - const msgs = [] - setTimeout(() => { - q.throw(new Error('expected error')) - q.push('c') // should no-op - }) - - await expect(async () => { - for await (const msg of q) { - msgs.push(msg) - } - }).rejects.toThrow('expected error') - - await wait(10) // wait for maybe push - // push('c') shouldn't have worked - expect(msgs).toEqual(['a', 'b']) - expect(q.length).toBe(0) - }) - - it('handles throw early', async () => { - const q = new PushQueue() - q.throw(new Error('expected error')) - q.push('c') // should no-op - - const msgs = [] - await expect(async () => { - for await (const msg of q) { - msgs.push(msg) - } - }).rejects.toThrow('expected error') - - await wait(10) // wait for maybe push - // push('c') shouldn't have worked - expect(q.length).toBe(0) - - expect(msgs).toEqual([]) - }) - - describe('abort', () => { - it('can be aborted', async () => { - const ac = new AbortController() - - const q = new PushQueue(['a', 'b'], { - signal: ac.signal, - }) - - setTimeout(() => { - ac.abort() - q.push('nope1') // should no-op - }) - - const msgs = [] - await expect(async () => { - for await (const msg of q) { - msgs.push(msg) - } - }).rejects.toThrow('abort') - - expect(msgs).toEqual(['a', 'b']) - }) - - it('handles aborting multiple buffers', async () => { - const ac = new AbortController() - - async function create(items = ['a', 'b']) { - const q = new PushQueue(items, { - signal: ac.signal, - }) - const msgs = [] - await expect(async () => { - for await (const msg of q) { - msgs.push(msg) - } - }).rejects.toThrow('abort') - await wait(10) // wait for maybe push - expect(q.length).toBe(0) - expect(msgs).toEqual(items) - } - - setTimeout(() => { - ac.abort() - }) - - await Promise.all([ - create(['a', 'b']), - create(['c', 'd']), - create([]), - ]) - }) - - it('can abort before iteration', async () => { - const ac = new AbortController() - - const q = new PushQueue(['a', 'b'], { - signal: ac.signal, - }) - - ac.abort() - q.push('nope1') // should no-op - const msgs = [] - await expect(async () => { - for await (const msg of q) { - msgs.push(msg) - } - }).rejects.toThrow('abort') - expect(msgs).toEqual(['a', 'b']) // still gives buffered items - }) - - it('can abort before creating PushQueue', async () => { - const ac = new AbortController() - ac.abort() - - const q = new PushQueue(['a', 'b'], { - signal: ac.signal, - }) - q.push('nope1') // should no-op - - const msgs = [] - await expect(async () => { - for await (const msg of q) { - msgs.push(msg) - } - }).rejects.toThrow('abort') - expect(msgs).toEqual(['a', 'b']) // still gives buffered items - }) - }) -}) From dff93e46459a53f4cf640c71e93cf855fc91e5bf Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 28 Sep 2020 22:13:26 -0400 Subject: [PATCH 074/517] Implement Resends. --- src/Publisher.js | 4 +- src/Stream.js | 176 ++++++++++++++++++++++++++------ test/integration/Stream.test.js | 89 ++++++++++++++++ 3 files changed, 238 insertions(+), 31 deletions(-) diff --git a/src/Publisher.js b/src/Publisher.js index 94dd67749..bf489b0e8 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -8,7 +8,7 @@ import { ethers } from 'ethers' import Signer from './Signer' import Stream from './rest/domain/Stream' import FailedToPublishError from './errors/FailedToPublishError' -import { CacheAsyncFn, CacheFn, LimitAsyncFnByKey } from './utils' +import { uuid, CacheAsyncFn, CacheFn, LimitAsyncFnByKey } from './utils' const { StreamMessage, MessageID, MessageRef } = MessageLayer @@ -303,7 +303,7 @@ export default class Publisher { await this.signer.signStreamMessage(streamMessage) } - const requestId = this.client.resender.resendUtil.generateRequestId() + const requestId = uuid('pub') const request = new ControlLayer.PublishRequest({ streamMessage, requestId, diff --git a/src/Stream.js b/src/Stream.js index b2b51f4f5..813fe009c 100644 --- a/src/Stream.js +++ b/src/Stream.js @@ -2,12 +2,17 @@ import { PassThrough } from 'stream' import pMemoize from 'p-memoize' import pLimit from 'p-limit' -import { ControlLayer } from 'streamr-client-protocol' +import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import AbortController from 'node-abort-controller' import { uuid } from './utils' -const { SubscribeRequest, UnsubscribeRequest, ControlMessage } = ControlLayer +const { + SubscribeRequest, UnsubscribeRequest, ControlMessage, + ResendLastRequest, ResendFromRequest, ResendRangeRequest, +} = ControlLayer + +const { MessageRef } = MessageLayer export class AbortError extends Error { constructor(msg = '', ...args) { @@ -47,29 +52,45 @@ function validateOptions(optionsOrStreamId) { return options } +const ResendResponses = [ControlMessage.TYPES.ResendResponseResending, ControlMessage.TYPES.ResendResponseNoResend] + const PAIRS = new Map([ - [ControlMessage.TYPES.SubscribeRequest, ControlMessage.TYPES.SubscribeResponse], - [ControlMessage.TYPES.UnsubscribeRequest, ControlMessage.TYPES.UnsubscribeResponse], + [ControlMessage.TYPES.SubscribeRequest, [ControlMessage.TYPES.SubscribeResponse]], + [ControlMessage.TYPES.UnsubscribeRequest, [ControlMessage.TYPES.UnsubscribeResponse]], + [ControlMessage.TYPES.ResendLastRequest, ResendResponses], + [ControlMessage.TYPES.ResendFromRequest, ResendResponses], + [ControlMessage.TYPES.ResendRangeRequest, ResendResponses], ]) -async function waitForResponse({ connection, type, requestId }) { +async function waitForResponse({ connection, types, requestId }) { return new Promise((resolve, reject) => { - let onErrorResponse + let cleanup const onResponse = (res) => { if (res.requestId !== requestId) { return } // clean up err handler - connection.off(ControlMessage.TYPES.ErrorResponse, onErrorResponse) + cleanup() resolve(res) } - onErrorResponse = (res) => { + + const onErrorResponse = (res) => { if (res.requestId !== requestId) { return } // clean up success handler - connection.off(type, onResponse) + cleanup() const error = new Error(res.errorMessage) error.code = res.errorCode reject(error) } - connection.on(type, onResponse) + + cleanup = () => { + types.forEach((type) => { + connection.off(type, onResponse) + }) + connection.off(ControlMessage.TYPES.ErrorResponse, onErrorResponse) + } + + types.forEach((type) => { + connection.on(type, onResponse) + }) connection.on(ControlMessage.TYPES.ErrorResponse, onErrorResponse) }) } @@ -77,7 +98,7 @@ async function waitForResponse({ connection, type, requestId }) { async function waitForRequestResponse(client, request) { return waitForResponse({ connection: client.connection, - type: PAIRS.get(request.type), + types: PAIRS.get(request.type), requestId: request.requestId, }) } @@ -127,7 +148,7 @@ async function unsubscribe(client, { streamId, streamPartition = 0 }) { * Executes finally function even if generator not started. */ -function iteratorFinally(iterator, onFinally) { +function iteratorFinally(iterator, onFinally = () => {}) { let started = false const g = (async function* It() { started = true @@ -156,8 +177,8 @@ function iteratorFinally(iterator, onFinally) { return g } -function messageIterator(client, { streamId, streamPartition, signal }) { - if (signal.aborted) { +function messageStream(client, { streamId, streamPartition, signal, type = ControlMessage.TYPES.BroadcastMessage }) { + if (signal && signal.aborted) { throw new AbortError() } @@ -169,9 +190,11 @@ function messageIterator(client, { streamId, streamPartition, signal }) { return queue.destroy(new AbortError()) } - signal.addEventListener('abort', onAbort, { - once: true - }) + if (signal) { + signal.addEventListener('abort', onAbort, { + once: true + }) + } const isMatchingStreamMessage = getIsMatchingStreamMessage({ streamId, @@ -184,9 +207,9 @@ function messageIterator(client, { streamId, streamPartition, signal }) { } } - client.connection.on(ControlMessage.TYPES.BroadcastMessage, onMessage) + client.connection.on(type, onMessage) queue.once('close', () => { - client.connection.off(ControlMessage.TYPES.BroadcastMessage, onMessage) + client.connection.off(type, onMessage) if (signal) { signal.removeEventListener('abort', onAbort, { once: true, @@ -206,7 +229,7 @@ class Subscription { this.client = client this.options = validateOptions(options) this.abortController = new AbortController() - this.iterators = new Set() + this.streams = new Set() this.queue = pLimit(1) const sub = this.subscribe.bind(this) @@ -247,32 +270,33 @@ class Subscription { } async return() { - await Promise.all([...this.iterators].map(async (it) => { + await Promise.all([...this.streams].map(async (it) => { await it.return() })) } async _cleanup(it) { // if iterator never started, finally block never called, thus need to manually clean it - this.iterators.delete(it) - if (!this.iterators.size) { - // unsubscribe if no more iterators + this.streams.delete(it) + if (!this.streams.size) { + // unsubscribe if no more streams await this.unsubscribe() } } count() { - return this.iterators.size + return this.streams.size } iterate() { - const it = iteratorFinally(messageIterator(this.client, { + const it = iteratorFinally(messageStream(this.client, { signal: this.abortController.signal, ...this.options, + type: ControlMessage.TYPES.BroadcastMessage, }), async () => ( this._cleanup(it) )) - this.iterators.add(it) + this.streams.add(it) return it } @@ -281,6 +305,50 @@ class Subscription { } } +async function resend(client, { requestId = uuid('rs'), streamId, streamPartition = 0, ...options } = {}) { + const sessionToken = await client.session.getSessionToken() + let request + if (options.last > 0) { + request = new ResendLastRequest({ + streamId, + streamPartition, + requestId, + numberLast: options.last, + sessionToken, + }) + } else if (options.from && !options.to) { + request = new ResendFromRequest({ + streamId, + streamPartition, + requestId, + fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), + publisherId: options.publisherId, + msgChainId: options.msgChainId, + sessionToken, + }) + } else if (options.from && options.to) { + request = new ResendRangeRequest({ + streamId, + streamPartition, + requestId, + fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), + toMsgRef: new MessageRef(options.to.timestamp, options.to.sequenceNumber), + publisherId: options.publisherId, + msgChainId: options.msgChainId, + sessionToken, + }) + } + + if (!request) { + throw new Error("Can't _requestResend without resend options") + } + + const onResponse = waitForRequestResponse(client, request) + + await client.send(request) + return onResponse +} + export default class Subscriptions { constructor(client) { this.client = client @@ -299,7 +367,57 @@ export default class Subscriptions { count(options) { const sub = this.get(options) - return sub ? sub.count() : -1 + return sub ? sub.count() : 0 + } + + async resend(opts) { + const options = validateOptions(opts) + const stream = messageStream(this.client, { + ...options, + type: ControlMessage.TYPES.UnicastMessage, + }) + + const it = iteratorFinally(stream) + + const requestId = uuid('rs') + // eslint-disable-next-line promise/catch-or-return + const onResendDone = waitForResponse({ + connection: this.client.connection, + types: [ + ControlMessage.TYPES.ResendResponseResent, + ControlMessage.TYPES.ResendResponseNoResend, + ], + requestId, + }).then(() => { + return stream.end() + }, (err) => { + return stream.destroy(err) + }) + + await Promise.race([ + resend(this.client, { + requestId, + ...options, + }), + onResendDone + ]) + + return it + } + + async resendSubscribe(options) { + const sub = await this.subscribe(options) + const resendSub = await this.resend(options) + + return iteratorFinally((async function* ResendSubIterator() { + yield* resendSub + yield* sub + }()), () => { + return Promise.all([ + resendSub.return(), + sub.return(), + ]) + }) } async subscribe(options) { @@ -322,6 +440,6 @@ export default class Subscriptions { await sub.queue(() => {}) } - await sub.return() // close all iterators (thus unsubscribe) + await sub.return() // close all streams (thus unsubscribe) } } diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index 3e1498c39..d17d1caeb 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -241,6 +241,95 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) }) + describe('resends', () => { + it('handles nothing to resend', async () => { + const M = new MessageStream(client) + const sub = await M.resend({ + streamId: stream.id, + last: 5, + }) + + const receivedMsgs = await collect(sub) + expect(receivedMsgs).toHaveLength(0) + expect(M.count(stream.id)).toBe(0) + }) + + describe('with resend data', () => { + let published + beforeEach(async () => { + published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + await wait(5000) + }, 10000) + + it('requests resend', async () => { + const M = new MessageStream(client) + const sub = await M.resend({ + streamId: stream.id, + last: published.length, + }) + + const receivedMsgs = await collect(sub) + expect(receivedMsgs).toHaveLength(published.length) + expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(M.count(stream.id)).toBe(0) + }) + + + describe('resendSubscribe', () => { + it('can request both', async () => { + const M = new MessageStream(client) + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, + }) + + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + const receivedMsgs = await collect(sub, async ({ received }) => { + if (received.length === published.length + 1) { + await sub.return() + } + }) + expect(receivedMsgs).toHaveLength(published.length + 1) + expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.concat(message)) + expect(M.count(stream.id)).toBe(0) + }, 6000) + + it('handles ending prematurely', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) + const M = new MessageStream(client) + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, + }) + + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + const receivedMsgs = await collect(sub, async ({ received }) => { + if (received.length === 3) { + await sub.return() + expect(unsubscribeEvents).toHaveLength(1) + } + }) + expect(receivedMsgs).toHaveLength(3) + expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 3)) + expect(M.count(stream.id)).toBe(0) + }, 6000) + }) + }) + }) + describe('mid-stream stop methods', () => { let sub1 let sub2 From 4ffbf5ee4b22a11ebef7a1e70049a272e71980d4 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 29 Sep 2020 13:20:25 -0400 Subject: [PATCH 075/517] Slightly refactor resend function. --- src/Stream.js | 50 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/src/Stream.js b/src/Stream.js index 813fe009c..3eefdaf13 100644 --- a/src/Stream.js +++ b/src/Stream.js @@ -305,43 +305,57 @@ class Subscription { } } -async function resend(client, { requestId = uuid('rs'), streamId, streamPartition = 0, ...options } = {}) { - const sessionToken = await client.session.getSessionToken() +function createResendRequest({ + requestId = uuid('rs'), + streamId, + streamPartition = 0, + publisherId, + msgChainId, + sessionToken, + ...options +}) { let request + const opts = { + streamId, + streamPartition, + requestId, + sessionToken, + } + if (options.last > 0) { request = new ResendLastRequest({ - streamId, - streamPartition, - requestId, + ...opts, numberLast: options.last, - sessionToken, }) } else if (options.from && !options.to) { request = new ResendFromRequest({ - streamId, - streamPartition, - requestId, + ...opts, fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), - publisherId: options.publisherId, - msgChainId: options.msgChainId, - sessionToken, + publisherId, + msgChainId, }) } else if (options.from && options.to) { request = new ResendRangeRequest({ - streamId, - streamPartition, - requestId, + ...opts, fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), toMsgRef: new MessageRef(options.to.timestamp, options.to.sequenceNumber), - publisherId: options.publisherId, - msgChainId: options.msgChainId, - sessionToken, + publisherId, + msgChainId, }) } if (!request) { throw new Error("Can't _requestResend without resend options") } + return request +} + +async function resend(client, options) { + const sessionToken = await client.session.getSessionToken() + const request = createResendRequest({ + ...options, + sessionToken, + }) const onResponse = waitForRequestResponse(client, request) From cd0aca9edb3d1863664e9295005b0272a168f218 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 29 Sep 2020 15:49:52 -0400 Subject: [PATCH 076/517] Return streams instead of iterators. --- src/Stream.js | 173 ++++++++---- src/StreamrClient.js | 26 +- src/Subscriber.js | 2 +- test/integration/Stream.test.js | 470 ++++++++++++++++++-------------- 4 files changed, 412 insertions(+), 259 deletions(-) diff --git a/src/Stream.js b/src/Stream.js index 3eefdaf13..ef54e3563 100644 --- a/src/Stream.js +++ b/src/Stream.js @@ -1,4 +1,5 @@ -import { PassThrough } from 'stream' +import { PassThrough, Readable, finished } from 'stream' +import { promisify } from 'util' import pMemoize from 'p-memoize' import pLimit from 'p-limit' @@ -7,6 +8,8 @@ import AbortController from 'node-abort-controller' import { uuid } from './utils' +const pFinished = promisify(finished) + const { SubscribeRequest, UnsubscribeRequest, ControlMessage, ResendLastRequest, ResendFromRequest, ResendRangeRequest, @@ -14,6 +17,8 @@ const { const { MessageRef } = MessageLayer +const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) + export class AbortError extends Error { constructor(msg = '', ...args) { super(`The operation was aborted. ${msg}`, ...args) @@ -113,6 +118,23 @@ function getIsMatchingStreamMessage({ streamId, streamPartition = 0 }) { } } +function pTimeout(p, ms, msg = '') { + let t + const start = Date.now() + return ( + Promise.race([ + p.finally(() => { + clearTimeout(t) + }), + new Promise(() => { + t = setTimeout((resolve, reject) => { + reject(new Error(`timed out: ${Date.now() - start}ms > ${ms}ms. ${msg}`)) + }) + }) + ]) + ) +} + async function subscribe(client, { streamId, streamPartition = 0 }) { const sessionToken = await client.session.getSessionToken() const request = new SubscribeRequest({ @@ -177,14 +199,61 @@ function iteratorFinally(iterator, onFinally = () => {}) { return g } +function addBeforeDestroy(stream) { + const d = stream.destroy.bind(stream) + const destroyFns = new Set() + // eslint-disable-next-line no-param-reassign + stream.destroy = async (...args) => { + if (!destroyFns || !destroyFns.size) { + return d(...args) + } + try { + for (const fn of destroyFns) { + // eslint-disable-next-line no-await-in-loop + await fn() + } + } catch (error) { + return d(error, ...args.slice(1)) + } finally { + destroyFns.clear() + } + + return d(...args) + } + + // eslint-disable-next-line no-param-reassign + stream.beforeDestroy = (fn) => { + destroyFns.add(fn) + } + + // eslint-disable-next-line no-param-reassign + stream.throw = async (err) => { + const it = stream[Symbol.asyncIterator]() + await it.throw(err) + await pFinished(stream) + } + + // eslint-disable-next-line no-param-reassign + stream.return = async () => { + // little trick to ensure stream cleaned up + // iterator.return won't exit until destroy handler finished + const it = stream[Symbol.asyncIterator]() + await it.return() + await stream.destroy() + await pFinished(stream) + } + + return stream +} + function messageStream(client, { streamId, streamPartition, signal, type = ControlMessage.TYPES.BroadcastMessage }) { if (signal && signal.aborted) { throw new AbortError() } - const queue = new PassThrough({ + const queue = addBeforeDestroy(new PassThrough({ objectMode: true, - }) + })) const onAbort = () => { return queue.destroy(new AbortError()) @@ -236,7 +305,6 @@ class Subscription { const unsub = this.unsubscribe.bind(this) this.subscribe = () => this.queue(sub) this.unsubscribe = () => this.queue(unsub) - this.return = this.return.bind(this) this.sendSubscribe = pMemoize(this.sendSubscribe.bind(this)) this.sendUnsubscribe = pMemoize(this.sendUnsubscribe.bind(this)) } @@ -270,14 +338,14 @@ class Subscription { } async return() { - await Promise.all([...this.streams].map(async (it) => { - await it.return() + await Promise.all([...this.streams].map(async (stream) => { + await stream.return() })) } - async _cleanup(it) { - // if iterator never started, finally block never called, thus need to manually clean it - this.streams.delete(it) + async _cleanup(stream) { + // if stream never started, finally block never called, thus need to manually clean it + this.streams.delete(stream) if (!this.streams.size) { // unsubscribe if no more streams await this.unsubscribe() @@ -289,15 +357,16 @@ class Subscription { } iterate() { - const it = iteratorFinally(messageStream(this.client, { + const stream = messageStream(this.client, { signal: this.abortController.signal, ...this.options, type: ControlMessage.TYPES.BroadcastMessage, - }), async () => ( - this._cleanup(it) - )) - this.streams.add(it) - return it + }) + stream.beforeDestroy(async () => { + await this._cleanup(stream) + }) + this.streams.add(stream) + return stream } [Symbol.asyncIterator]() { @@ -384,6 +453,29 @@ export default class Subscriptions { return sub ? sub.count() : 0 } + async subscribe(options) { + const key = SubKey(validateOptions(options)) + const sub = ( + this.subscriptions.get(key) + || this.subscriptions.set(key, new Subscription(this.client, options)).get(key) + ) + + return sub.subscribe() + } + + async unsubscribe(options) { + const key = SubKey(validateOptions(options)) + const sub = this.subscriptions.get(key) + if (!sub) { return } + + // wait for any outstanding operations + if (sub.hasPending()) { + await sub.queue(() => {}) + } + + await sub.return() // close all streams (thus unsubscribe) + } + async resend(opts) { const options = validateOptions(opts) const stream = messageStream(this.client, { @@ -391,8 +483,6 @@ export default class Subscriptions { type: ControlMessage.TYPES.UnicastMessage, }) - const it = iteratorFinally(stream) - const requestId = uuid('rs') // eslint-disable-next-line promise/catch-or-return const onResendDone = waitForResponse({ @@ -403,9 +493,10 @@ export default class Subscriptions { ], requestId, }).then(() => { - return stream.end() + // close off resend + return stream.push(null) }, (err) => { - return stream.destroy(err) + return stream.throw(err) }) await Promise.race([ @@ -416,44 +507,32 @@ export default class Subscriptions { onResendDone ]) - return it + return stream } async resendSubscribe(options) { - const sub = await this.subscribe(options) - const resendSub = await this.resend(options) + const [sub, resendSub] = await Promise.all([ + this.subscribe(options), + this.resend(options), + ]) - return iteratorFinally((async function* ResendSubIterator() { + const it = iteratorFinally((async function* ResendSubIterator() { yield* resendSub yield* sub - }()), () => { - return Promise.all([ + }()), async () => { + await Promise.all([ + resendSub.return(), + sub.return(), + ]) + }) + const stream = addBeforeDestroy(Readable.from(it)) + stream.beforeDestroy(async () => { + await Promise.all([ resendSub.return(), sub.return(), ]) }) - } - - async subscribe(options) { - const key = SubKey(validateOptions(options)) - const sub = ( - this.subscriptions.get(key) - || this.subscriptions.set(key, new Subscription(this.client, options)).get(key) - ) - - return sub.subscribe() - } - - async unsubscribe(options) { - const key = SubKey(validateOptions(options)) - const sub = this.subscriptions.get(key) - if (!sub) { return } - - // wait for any outstanding operations - if (sub.hasPending()) { - await sub.queue(() => {}) - } - await sub.return() // close all streams (thus unsubscribe) + return stream } } diff --git a/src/StreamrClient.js b/src/StreamrClient.js index d047dc7a6..8ac27f3c7 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -11,6 +11,7 @@ import { getVersionString } from './utils' import Publisher from './Publisher' import Resender from './Resender' import Subscriber from './Subscriber' +import MessageStream from './Stream' const { ControlMessage } = ControlLayer @@ -107,8 +108,9 @@ export default class StreamrClient extends EventEmitter { }) this.publisher = new Publisher(this) - this.subscriber = new Subscriber(this) - this.resender = new Resender(this) + this.messageStream = new MessageStream(this) + //this.subscriber = new Subscriber(this) + //this.resender = new Resender(this) this.connection.on('connected', this.onConnectionConnected) this.connection.on('disconnected', this.onConnectionDisconnected) @@ -130,7 +132,7 @@ export default class StreamrClient extends EventEmitter { onConnectionError(err) { // If there is an error parsing a json message in a stream, fire error events on the relevant subs if ((err instanceof Errors.InvalidJsonError || err.reason instanceof Errors.InvalidJsonError)) { - this.subscriber.onErrorMessage(err) + //this.subscriber.onErrorMessage(err) } else { // if it looks like an error emit as-is, otherwise wrap in new Error this.emit('error', new Connection.ConnectionError(err)) @@ -207,7 +209,7 @@ export default class StreamrClient extends EventEmitter { disconnect() { this.publisher.stop() - this.subscriber.stop() + //this.subscriber.stop() return this.connection.disconnect() } @@ -224,20 +226,20 @@ export default class StreamrClient extends EventEmitter { } subscribe(...args) { - return this.subscriber.subscribe(...args) + return this.messageStream.subscribe(...args) } unsubscribe(...args) { - return this.subscriber.unsubscribe(...args) + return this.messageStream.unsubscribe(...args) } - unsubscribeAll(...args) { - return this.subscriber.unsubscribeAll(...args) - } + //unsubscribeAll(...args) { + //return this.subscriber.unsubscribeAll(...args) + //} - getSubscriptions(...args) { - return this.subscriber.getSubscriptions(...args) - } + //getSubscriptions(...args) { + //return this.subscriber.getSubscriptions(...args) + //} async ensureConnected() { return this.connect() diff --git a/src/Subscriber.js b/src/Subscriber.js index ea66303a9..08183db2f 100644 --- a/src/Subscriber.js +++ b/src/Subscriber.js @@ -110,8 +110,8 @@ export default class Subscriber { this.debug('WARN: InvalidJsonError received for stream with no subscriptions: %s', err.streamId) } } - async subscribe(...args) { + const sub = this.createSubscription(...args) await Promise.all([ sub.waitForSubscribed(), diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index d17d1caeb..7f82d114c 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -1,3 +1,6 @@ +import { Readable, finished } from 'stream' +import { promisify } from 'util' + import { ControlLayer } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' @@ -10,6 +13,8 @@ import config from './config' const { ControlMessage } = ControlLayer +const pFinished = promisify(finished) + const Msg = (opts) => ({ value: uid('msg'), ...opts, @@ -27,7 +32,7 @@ async function collect(iterator, fn = () => {}) { return received } -const TEST_REPEATS = 5 +const TEST_REPEATS = 1 describe('StreamrClient Stream', () => { let expectErrors = 0 // check no errors by default @@ -52,6 +57,9 @@ describe('StreamrClient Stream', () => { } beforeEach(async () => { + if (client) { + await client.disconnect() + } client = createClient() await client.connect() console.log = client.debug @@ -98,235 +106,172 @@ describe('StreamrClient Stream', () => { for (let k = 0; k < TEST_REPEATS; k++) { // eslint-disable-next-line no-loop-func describe(`test repeat ${k + 1} of ${TEST_REPEATS}`, () => { - it('can subscribe to stream and get updates then auto unsubscribe', async () => { - const M = new MessageStream(client) - const sub = await M.subscribe(stream.id) - expect(M.count(stream.id)).toBe(1) - - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + describe('basics', () => { + it('can subscribe to stream and get updates then auto unsubscribe', async () => { + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) - const received = [] - for await (const m of sub) { - received.push(m) - if (received.length === published.length) { - await sub.return() + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) } - } - expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) - expect(M.count(stream.id)).toBe(0) - }) - it('can subscribe to stream multiple times, get updates then unsubscribe', async () => { - const M = new MessageStream(client) - const sub1 = await M.subscribe(stream.id) - const sub2 = await M.subscribe(stream.id) - - expect(M.count(stream.id)).toBe(2) - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } - - const [received1, received2] = await Promise.all([ - collect(sub1, ({ received, iterator }) => { - if (received.length === published.length) { - iterator.return() - } - }), - collect(sub2, ({ received, iterator }) => { + const received = [] + for await (const m of sub) { + received.push(m) if (received.length === published.length) { - iterator.return() + await sub.return() } - }), - ]) - - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) - expect(received2).toEqual(received1) - expect(M.count(stream.id)).toBe(0) - }) + } + expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(M.count(stream.id)).toBe(0) + }) - it('can subscribe to stream multiple times in parallel, get updates then unsubscribe', async () => { - const M = new MessageStream(client) - const [sub1, sub2] = await Promise.all([ - M.subscribe(stream.id), - M.subscribe(stream.id), - ]) + it('can kill stream using destroy', async () => { + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) - expect(M.count(stream.id)).toBe(2) - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } - const [received1, received2] = await Promise.all([ - collect(sub1, ({ received, iterator }) => { + const received = [] + for await (const m of sub) { + received.push(m) if (received.length === published.length) { - iterator.return() + await sub.destroy() } - }), - collect(sub2, ({ received, iterator }) => { - if (received.length === published.length) { - iterator.return() - } - }) - ]) - - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) - expect(received2).toEqual(received1) - expect(M.count(stream.id)).toBe(0) - }) - - it('can subscribe to stream and get some updates then unsubscribe mid-stream', async () => { - const M = new MessageStream(client) - const sub = await M.subscribe(stream.id) - expect(M.count(stream.id)).toBe(1) + } + await pFinished(sub) + expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(M.count(stream.id)).toBe(0) + }) - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + it('can subscribe to stream multiple times, get updates then unsubscribe', async () => { + const M = new MessageStream(client) + const sub1 = await M.subscribe(stream.id) + const sub2 = await M.subscribe(stream.id) - const receivedMsgs = await collect(sub, ({ received, iterator }) => { - if (received.length === 1) { - iterator.return() + expect(M.count(stream.id)).toBe(2) + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) } - }) - expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) - expect(M.count(stream.id)).toBe(0) - }) + const [received1, received2] = await Promise.all([ + collect(sub1, async ({ received, iterator }) => { + if (received.length === published.length) { + await iterator.return() + } + }), + collect(sub2, async ({ received, iterator }) => { + if (received.length === published.length) { + await iterator.return() + } + }), + ]) - it('finishes unsubscribe before returning', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(received2).toEqual(received1) + expect(M.count(stream.id)).toBe(0) }) - const M = new MessageStream(client) - const sub = await M.subscribe(stream.id) - - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + it('can subscribe to stream multiple times in parallel, get updates then unsubscribe', async () => { + const M = new MessageStream(client) + const [sub1, sub2] = await Promise.all([ + M.subscribe(stream.id), + M.subscribe(stream.id), + ]) - const receivedMsgs = await collect(sub, async ({ received }) => { - if (received.length === 2) { - expect(unsubscribeEvents).toHaveLength(0) - await sub.return() - expect(unsubscribeEvents).toHaveLength(1) - expect(M.count(stream.id)).toBe(0) + expect(M.count(stream.id)).toBe(2) + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) } - }) - expect(receivedMsgs).toHaveLength(2) - expect(M.count(stream.id)).toBe(0) - }) - - describe('resends', () => { - it('handles nothing to resend', async () => { - const M = new MessageStream(client) - const sub = await M.resend({ - streamId: stream.id, - last: 5, - }) + const [received1, received2] = await Promise.all([ + collect(sub1, ({ received, iterator }) => { + if (received.length === published.length) { + iterator.return() + } + }), + collect(sub2, ({ received, iterator }) => { + if (received.length === published.length) { + iterator.return() + } + }) + ]) - const receivedMsgs = await collect(sub) - expect(receivedMsgs).toHaveLength(0) + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(received2).toEqual(received1) expect(M.count(stream.id)).toBe(0) }) - describe('with resend data', () => { - let published - beforeEach(async () => { - published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } - await wait(5000) - }, 10000) + it('can subscribe to stream and get some updates then unsubscribe mid-stream', async () => { + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) - it('requests resend', async () => { - const M = new MessageStream(client) - const sub = await M.resend({ - streamId: stream.id, - last: published.length, - }) + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } - const receivedMsgs = await collect(sub) - expect(receivedMsgs).toHaveLength(published.length) - expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) - expect(M.count(stream.id)).toBe(0) + const receivedMsgs = await collect(sub, async ({ received, iterator }) => { + if (received.length === 1) { + await iterator.return() + } }) + expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(M.count(stream.id)).toBe(0) + }) - describe('resendSubscribe', () => { - it('can request both', async () => { - const M = new MessageStream(client) - const sub = await M.resendSubscribe({ - streamId: stream.id, - last: published.length, - }) - - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - const receivedMsgs = await collect(sub, async ({ received }) => { - if (received.length === published.length + 1) { - await sub.return() - } - }) - expect(receivedMsgs).toHaveLength(published.length + 1) - expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.concat(message)) - expect(M.count(stream.id)).toBe(0) - }, 6000) + it('finishes unsubscribe before returning', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) - it('handles ending prematurely', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) - const M = new MessageStream(client) - const sub = await M.resendSubscribe({ - streamId: stream.id, - last: published.length, - }) + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - const receivedMsgs = await collect(sub, async ({ received }) => { - if (received.length === 3) { - await sub.return() - expect(unsubscribeEvents).toHaveLength(1) - } - }) - expect(receivedMsgs).toHaveLength(3) - expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 3)) + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + const receivedMsgs = await collect(sub, async ({ received }) => { + if (received.length === 2) { + expect(unsubscribeEvents).toHaveLength(0) + await sub.return() + expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) - }, 6000) + } }) + + expect(receivedMsgs).toHaveLength(2) + expect(M.count(stream.id)).toBe(0) }) }) @@ -361,7 +306,7 @@ describe('StreamrClient Stream', () => { } }), ]) - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 2)) expect(received2.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) expect(M.count(stream.id)).toBe(0) }) @@ -381,7 +326,7 @@ describe('StreamrClient Stream', () => { })).rejects.toThrow('abort'), ]) - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 2)) expect(received2.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) expect(M.count(stream.id)).toBe(0) @@ -459,6 +404,133 @@ describe('StreamrClient Stream', () => { expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) }) + + describe('resends', () => { + it('handles nothing to resend', async () => { + const M = new MessageStream(client) + const sub = await M.resend({ + streamId: stream.id, + last: 5, + }) + + const receivedMsgs = await collect(sub) + expect(receivedMsgs).toHaveLength(0) + expect(M.count(stream.id)).toBe(0) + }) + + describe('with resend data', () => { + let published + beforeEach(async () => { + published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + await wait(10000) + }, 20000) + + it('can destroy stream', async () => { + const M = new MessageStream(client) + const sub = await M.resend({ + streamId: stream.id, + last: published.length, + }) + + const received = [] + for await (const m of sub) { + received.push(m) + if (received.length === published.length) { + await sub.destroy() + } + } + await pFinished(sub) + }) + + it('requests resend', async () => { + const M = new MessageStream(client) + const sub = await M.resend({ + streamId: stream.id, + last: published.length, + }) + + const receivedMsgs = await collect(sub) + expect(receivedMsgs).toHaveLength(published.length) + expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(M.count(stream.id)).toBe(0) + }) + + describe('resendSubscribe', () => { + it('can destroy stream', async () => { + const M = new MessageStream(client) + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, + }) + + expect(M.count(stream.id)).toBe(1) + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + await sub.destroy() + + const received = [] + for await (const m of sub) { + received.push(m) + } + expect(received).toHaveLength(0) + await pFinished(sub) + expect(M.count(stream.id)).toBe(0) + }, 10000) + + it('can request both', async () => { + const M = new MessageStream(client) + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, + }) + + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + const receivedMsgs = await collect(sub, async ({ received }) => { + if (received.length === published.length + 1) { + await sub.destroy() + } + }) + expect(receivedMsgs).toHaveLength(published.length + 1) + expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.concat(message)) + expect(M.count(stream.id)).toBe(0) + }, 6000) + + it('handles ending prematurely', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) + const M = new MessageStream(client) + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, + }) + + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + const receivedMsgs = await collect(sub, async ({ received }) => { + if (received.length === 3) { + await sub.return() + expect(unsubscribeEvents).toHaveLength(1) + } + }) + expect(receivedMsgs).toHaveLength(3) + expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 3)) + expect(M.count(stream.id)).toBe(0) + }, 6000) + }) + }) + }) }) } }) From 5ae07e2159107276bf9db269b4bc09d69089654d Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 29 Sep 2020 16:42:41 -0400 Subject: [PATCH 077/517] Support both nested & top level resend options. --- src/Stream.js | 28 +++++++++++++++++++--------- test/integration/Stream.test.js | 2 ++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/Stream.js b/src/Stream.js index ef54e3563..72300ba74 100644 --- a/src/Stream.js +++ b/src/Stream.js @@ -378,8 +378,6 @@ function createResendRequest({ requestId = uuid('rs'), streamId, streamPartition = 0, - publisherId, - msgChainId, sessionToken, ...options }) { @@ -391,23 +389,34 @@ function createResendRequest({ sessionToken, } - if (options.last > 0) { + const { + from, + to, + last, + publisherId, + msgChainId, + } = { + ...options, + ...options.resend + } + + if (last > 0) { request = new ResendLastRequest({ ...opts, - numberLast: options.last, + numberLast: last, }) - } else if (options.from && !options.to) { + } else if (from && !to) { request = new ResendFromRequest({ ...opts, - fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), + fromMsgRef: new MessageRef(from.timestamp, from.sequenceNumber), publisherId, msgChainId, }) - } else if (options.from && options.to) { + } else if (from && to) { request = new ResendRangeRequest({ ...opts, - fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), - toMsgRef: new MessageRef(options.to.timestamp, options.to.sequenceNumber), + fromMsgRef: new MessageRef(from.timestamp, from.sequenceNumber), + toMsgRef: new MessageRef(to.timestamp, to.sequenceNumber), publisherId, msgChainId, }) @@ -416,6 +425,7 @@ function createResendRequest({ if (!request) { throw new Error("Can't _requestResend without resend options") } + return request } diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index 7f82d114c..9632e6990 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -60,6 +60,7 @@ describe('StreamrClient Stream', () => { if (client) { await client.disconnect() } + // eslint-disable-next-line require-atomic-updates client = createClient() await client.connect() console.log = client.debug @@ -524,6 +525,7 @@ describe('StreamrClient Stream', () => { expect(unsubscribeEvents).toHaveLength(1) } }) + expect(receivedMsgs).toHaveLength(3) expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 3)) expect(M.count(stream.id)).toBe(0) From 68b0094c776539904fea1bc53908df81d5ad1b7f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 1 Oct 2020 12:48:10 -0400 Subject: [PATCH 078/517] Add getStreamLast endpoint. Update integration test config rest route to support broker api calls. --- src/Stream.js | 2 +- src/rest/StreamEndpoints.js | 23 ++++++++++++++++++++++- test/integration/StreamEndpoints.test.js | 13 +++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/Stream.js b/src/Stream.js index 72300ba74..e47523efe 100644 --- a/src/Stream.js +++ b/src/Stream.js @@ -28,7 +28,7 @@ export class AbortError extends Error { } } -function validateOptions(optionsOrStreamId) { +export function validateOptions(optionsOrStreamId) { if (!optionsOrStreamId) { throw new Error('options is required!') } diff --git a/src/rest/StreamEndpoints.js b/src/rest/StreamEndpoints.js index 064504814..874f73fc3 100644 --- a/src/rest/StreamEndpoints.js +++ b/src/rest/StreamEndpoints.js @@ -4,11 +4,12 @@ import { Agent as HttpsAgent } from 'https' import qs from 'qs' import debugFactory from 'debug' -import { getEndpointUrl } from '../utils' +import { validateOptions, getEndpointUrl } from '../Stream' import Stream from './domain/Stream' import authFetch from './authFetch' + const debug = debugFactory('StreamrClient') const agentSettings = { @@ -75,6 +76,10 @@ export async function createStream(props) { this.debug('createStream', { props, }) + if (!props || !props.name) { + throw new Error('Stream properties must contain a "name" field!') + } + const json = await authFetch( `${this.options.restUrl}/streams`, this.session, @@ -175,6 +180,22 @@ export async function getStreamValidationInfo(streamId) { return json } +export async function getStreamLast(streamObjectOrId) { + const { streamId, streamPartition = 0, count = 1 } = validateOptions(streamObjectOrId) + this.debug('getStreamLast', { + streamId, + streamPartition, + count, + }) + const query = { + count, + } + + const url = `${this.options.restUrl}/streams/${streamId}/data/partitions/${streamPartition}/last?${qs.stringify(query)}` + const json = await authFetch(url, this.session) + return json +} + export async function publishHttp(streamObjectOrId, data, requestOptions = {}, keepAlive = true) { let streamId if (streamObjectOrId instanceof Stream) { diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.js index 6a2fc5124..0aedd4dfa 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.js @@ -114,6 +114,19 @@ describe('StreamEndpoints', () => { }) }) + describe('getStreamLast', () => { + it('does not error', async () => { + const stream = await client.createStream({ + name, + requireSignedData: true, + requireEncryptedData: false, + }) + createdStream = stream + const result = await client.getStreamLast(createdStream.id) + expect(result).toEqual([]) + }) + }) + describe('getStreamPublishers', () => { it('retrieves a list of publishers', async () => { const publishers = await client.getStreamPublishers(createdStream.id) From 6412f63517c6a0872fdb683128ffa7d1d17bcb42 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 2 Oct 2020 09:34:09 -0400 Subject: [PATCH 079/517] Move resend tests into StreamResends file. Note inconsistent last message results from api. --- src/Stream.js | 427 ++++++++++++------------- src/utils.js | 27 ++ test/integration/Stream.test.js | 173 +++------- test/integration/StreamResends.test.js | 380 ++++++++++++++++++++++ 4 files changed, 652 insertions(+), 355 deletions(-) create mode 100644 test/integration/StreamResends.test.js diff --git a/src/Stream.js b/src/Stream.js index e47523efe..fb9a145d5 100644 --- a/src/Stream.js +++ b/src/Stream.js @@ -1,4 +1,4 @@ -import { PassThrough, Readable, finished } from 'stream' +import { PassThrough, finished } from 'stream' import { promisify } from 'util' import pMemoize from 'p-memoize' @@ -6,7 +6,7 @@ import pLimit from 'p-limit' import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import AbortController from 'node-abort-controller' -import { uuid } from './utils' +import { uuid, Defer } from './utils' const pFinished = promisify(finished) @@ -17,8 +17,6 @@ const { const { MessageRef } = MessageLayer -const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) - export class AbortError extends Error { constructor(msg = '', ...args) { super(`The operation was aborted. ${msg}`, ...args) @@ -28,6 +26,44 @@ export class AbortError extends Error { } } +/** + * Allows injecting a function to execute after an iterator finishes. + * Executes finally function even if generator not started. + */ + +function iteratorFinally(iterator, onFinally = () => {}) { + let started = false + let finallyRun = false + const g = (async function* It() { + started = true + try { + yield* iterator + } finally { + finallyRun = true + await onFinally(iterator) + } + }()) + + // overrides return/throw to call onFinally even if generator was never started + const oldReturn = g.return + g.return = async (...args) => { + if (!started && !finallyRun) { + finallyRun = true + await onFinally(iterator) + } + return oldReturn.call(g, ...args) + } + const oldThrow = g.throw + g.throw = async (...args) => { + if (!started && !finallyRun) { + finallyRun = true + await onFinally(iterator) + } + return oldThrow.call(g, ...args) + } + return g +} + export function validateOptions(optionsOrStreamId) { if (!optionsOrStreamId) { throw new Error('options is required!') @@ -118,23 +154,6 @@ function getIsMatchingStreamMessage({ streamId, streamPartition = 0 }) { } } -function pTimeout(p, ms, msg = '') { - let t - const start = Date.now() - return ( - Promise.race([ - p.finally(() => { - clearTimeout(t) - }), - new Promise(() => { - t = setTimeout((resolve, reject) => { - reject(new Error(`timed out: ${Date.now() - start}ms > ${ms}ms. ${msg}`)) - }) - }) - ]) - ) -} - async function subscribe(client, { streamId, streamPartition = 0 }) { const sessionToken = await client.session.getSessionToken() const request = new SubscribeRequest({ @@ -165,85 +184,62 @@ async function unsubscribe(client, { streamId, streamPartition = 0 }) { return onResponse } -/** - * Allows injecting a function to execute after an iterator finishes. - * Executes finally function even if generator not started. - */ - -function iteratorFinally(iterator, onFinally = () => {}) { - let started = false - const g = (async function* It() { - started = true - try { - yield* iterator - } finally { - await onFinally(iterator) - } - }()) - - // overrides return/throw to call onFinally even if generator was never started - const oldReturn = g.return - g.return = async (...args) => { - if (!started) { - await onFinally(iterator) - } - return oldReturn.call(g, ...args) - } - const oldThrow = g.throw - g.throw = async (...args) => { - if (!started) { - await onFinally(iterator) - } - return oldThrow.call(g, ...args) +function createResendRequest({ + requestId = uuid('rs'), + streamId, + streamPartition = 0, + publisherId, + msgChainId, + sessionToken, + ...options +}) { + let request + const opts = { + streamId, + streamPartition, + requestId, + sessionToken, } - return g -} - -function addBeforeDestroy(stream) { - const d = stream.destroy.bind(stream) - const destroyFns = new Set() - // eslint-disable-next-line no-param-reassign - stream.destroy = async (...args) => { - if (!destroyFns || !destroyFns.size) { - return d(...args) - } - try { - for (const fn of destroyFns) { - // eslint-disable-next-line no-await-in-loop - await fn() - } - } catch (error) { - return d(error, ...args.slice(1)) - } finally { - destroyFns.clear() - } - return d(...args) + if (options.last > 0) { + request = new ResendLastRequest({ + ...opts, + numberLast: options.last, + }) + } else if (options.from && !options.to) { + request = new ResendFromRequest({ + ...opts, + fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), + publisherId, + msgChainId, + }) + } else if (options.from && options.to) { + request = new ResendRangeRequest({ + ...opts, + fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), + toMsgRef: new MessageRef(options.to.timestamp, options.to.sequenceNumber), + publisherId, + msgChainId, + }) } - // eslint-disable-next-line no-param-reassign - stream.beforeDestroy = (fn) => { - destroyFns.add(fn) + if (!request) { + throw new Error("Can't _requestResend without resend options") } + return request +} - // eslint-disable-next-line no-param-reassign - stream.throw = async (err) => { - const it = stream[Symbol.asyncIterator]() - await it.throw(err) - await pFinished(stream) - } +async function resend(client, options) { + const sessionToken = await client.session.getSessionToken() + const request = createResendRequest({ + ...options, + sessionToken, + }) - // eslint-disable-next-line no-param-reassign - stream.return = async () => { - // little trick to ensure stream cleaned up - // iterator.return won't exit until destroy handler finished - const it = stream[Symbol.asyncIterator]() - await it.return() - await stream.destroy() - await pFinished(stream) - } + const onResponse = waitForRequestResponse(client, request) - return stream + await client.send(request) + return onResponse } function messageStream(client, { streamId, streamPartition, signal, type = ControlMessage.TYPES.BroadcastMessage }) { @@ -251,9 +247,9 @@ function messageStream(client, { streamId, streamPartition, signal, type = Contr throw new AbortError() } - const queue = addBeforeDestroy(new PassThrough({ + const queue = new PassThrough({ objectMode: true, - })) + }) const onAbort = () => { return queue.destroy(new AbortError()) @@ -277,14 +273,24 @@ function messageStream(client, { streamId, streamPartition, signal, type = Contr } client.connection.on(type, onMessage) - queue.once('close', () => { + const onClose = () => { + queue.off('close', onClose) + queue.off('end', onClose) + queue.off('destroy', onClose) + queue.off('finish', onClose) client.connection.off(type, onMessage) if (signal) { signal.removeEventListener('abort', onAbort, { once: true, }) } - }) + } + + queue.once('close', onClose) + queue.once('end', onClose) + queue.once('destroy', onClose) + queue.once('finish', onClose) + return queue } @@ -293,6 +299,30 @@ function SubKey({ streamId, streamPartition = 0 }) { return `${streamId}|${streamPartition}` } +function streamIterator(stream, abortController) { + const it = iteratorFinally((async function* streamIteratorFn() { + yield* stream + }()), async () => { + stream.destroy() + await pFinished(stream, { + readable: false, + writable: false, + }) + }) + + it.stream = stream + it.abort = () => abortController && abortController.abort() + it.stop = async () => { + stream.destroy() + await pFinished(stream, { + readable: false, + writable: false, + }) + await it.return() + } + return it +} + class Subscription { constructor(client, options) { this.client = client @@ -305,6 +335,7 @@ class Subscription { const unsub = this.unsubscribe.bind(this) this.subscribe = () => this.queue(sub) this.unsubscribe = () => this.queue(unsub) + this.return = this.return.bind(this) this.sendSubscribe = pMemoize(this.sendSubscribe.bind(this)) this.sendUnsubscribe = pMemoize(this.sendUnsubscribe.bind(this)) } @@ -329,7 +360,8 @@ class Subscription { async subscribe() { pMemoize.clear(this.sendUnsubscribe) await this.sendSubscribe() - return this.iterate() + this.initialIterator = this.iterate() + return this } async unsubscribe() { @@ -337,15 +369,21 @@ class Subscription { await this.sendUnsubscribe() } + async stop() { + await Promise.all([...this.streams].map(async (it) => { + await it.stop() + })) + } + async return() { - await Promise.all([...this.streams].map(async (stream) => { - await stream.return() + await Promise.all([...this.streams].map(async (it) => { + await it.return() })) } - async _cleanup(stream) { - // if stream never started, finally block never called, thus need to manually clean it - this.streams.delete(stream) + async _cleanup(it) { + // if iterator never started, finally block never called, thus need to manually clean it + this.streams.delete(it) if (!this.streams.size) { // unsubscribe if no more streams await this.unsubscribe() @@ -362,86 +400,32 @@ class Subscription { ...this.options, type: ControlMessage.TYPES.BroadcastMessage, }) - stream.beforeDestroy(async () => { - await this._cleanup(stream) + const streamIt = streamIterator(stream) + const it = iteratorFinally(streamIt, async () => { + await this._cleanup(it) }) - this.streams.add(stream) - return stream + it.stream = stream + it.stop = async () => { + await streamIt.stop() + await it.return() + } + this.streams.add(it) + + return it } [Symbol.asyncIterator]() { + if (this.initialIterator) { + const it = this.initialIterator + delete this.initialIterator + if (this.streams.has(it)) { + return it + } + } return this.iterate() } } -function createResendRequest({ - requestId = uuid('rs'), - streamId, - streamPartition = 0, - sessionToken, - ...options -}) { - let request - const opts = { - streamId, - streamPartition, - requestId, - sessionToken, - } - - const { - from, - to, - last, - publisherId, - msgChainId, - } = { - ...options, - ...options.resend - } - - if (last > 0) { - request = new ResendLastRequest({ - ...opts, - numberLast: last, - }) - } else if (from && !to) { - request = new ResendFromRequest({ - ...opts, - fromMsgRef: new MessageRef(from.timestamp, from.sequenceNumber), - publisherId, - msgChainId, - }) - } else if (from && to) { - request = new ResendRangeRequest({ - ...opts, - fromMsgRef: new MessageRef(from.timestamp, from.sequenceNumber), - toMsgRef: new MessageRef(to.timestamp, to.sequenceNumber), - publisherId, - msgChainId, - }) - } - - if (!request) { - throw new Error("Can't _requestResend without resend options") - } - - return request -} - -async function resend(client, options) { - const sessionToken = await client.session.getSessionToken() - const request = createResendRequest({ - ...options, - sessionToken, - }) - - const onResponse = waitForRequestResponse(client, request) - - await client.send(request) - return onResponse -} - export default class Subscriptions { constructor(client) { this.client = client @@ -463,29 +447,6 @@ export default class Subscriptions { return sub ? sub.count() : 0 } - async subscribe(options) { - const key = SubKey(validateOptions(options)) - const sub = ( - this.subscriptions.get(key) - || this.subscriptions.set(key, new Subscription(this.client, options)).get(key) - ) - - return sub.subscribe() - } - - async unsubscribe(options) { - const key = SubKey(validateOptions(options)) - const sub = this.subscriptions.get(key) - if (!sub) { return } - - // wait for any outstanding operations - if (sub.hasPending()) { - await sub.queue(() => {}) - } - - await sub.return() // close all streams (thus unsubscribe) - } - async resend(opts) { const options = validateOptions(opts) const stream = messageStream(this.client, { @@ -493,6 +454,7 @@ export default class Subscriptions { type: ControlMessage.TYPES.UnicastMessage, }) + const it = streamIterator(stream) const requestId = uuid('rs') // eslint-disable-next-line promise/catch-or-return const onResendDone = waitForResponse({ @@ -502,11 +464,13 @@ export default class Subscriptions { ControlMessage.TYPES.ResendResponseNoResend, ], requestId, - }).then(() => { - // close off resend - return stream.push(null) + }).then((v) => { + if (stream.writable) { + stream.end() + } + return v }, (err) => { - return stream.throw(err) + return it.stop(err) }) await Promise.race([ @@ -517,32 +481,55 @@ export default class Subscriptions { onResendDone ]) - return stream + return it } async resendSubscribe(options) { - const [sub, resendSub] = await Promise.all([ - this.subscribe(options), - this.resend(options), - ]) + const sub = (await this.subscribe(options))[Symbol.asyncIterator]() + const resendSub = await this.resend(options) + const stop = async () => { + await Promise.all([ + sub.stop(), + resendSub.stop(), + ]) + } const it = iteratorFinally((async function* ResendSubIterator() { yield* resendSub yield* sub - }()), async () => { - await Promise.all([ - resendSub.return(), - sub.return(), - ]) - }) - const stream = addBeforeDestroy(Readable.from(it)) - stream.beforeDestroy(async () => { - await Promise.all([ - resendSub.return(), - sub.return(), - ]) - }) + }()), stop) + + it.abort = () => this.abortController && this.abortController.abort() + it.stop = async () => { + await stop() + return it.return() + } - return stream + it.resend = resendSub + it.realtime = sub + return it + } + + async subscribe(options) { + const key = SubKey(validateOptions(options)) + const sub = ( + this.subscriptions.get(key) + || this.subscriptions.set(key, new Subscription(this.client, options)).get(key) + ) + + return sub.subscribe() + } + + async unsubscribe(options) { + const key = SubKey(validateOptions(options)) + const sub = this.subscriptions.get(key) + if (!sub) { return } + + // wait for any outstanding operations + if (sub.hasPending()) { + await sub.queue(() => {}) + } + + await sub.return() // close all streams (thus unsubscribe) } } diff --git a/src/utils.js b/src/utils.js index 7ad6ee4e7..0b47cbfc7 100644 --- a/src/utils.js +++ b/src/utils.js @@ -138,3 +138,30 @@ export function LimitAsyncFnByKey(limit) { return f } +/** + * Deferred promise allowing external control of resolve/reject. + * Returns a Promise with resolve/reject functions attached. + * Also has a wrap(fn) method that wraps a function to settle this promise + * Defer optionally takes executor function ala `new Promise(executor)` + */ + +export function Defer(executor = () => {}) { + let resolve + let reject + // eslint-disable-next-line promise/param-names + const p = new Promise((_resolve, _reject) => { + resolve = _resolve + reject = _reject + executor(resolve, reject) + }) + + function wrap(fn) { + return async (...args) => Promise.resolve(fn(...args)).then(resolve, reject) + } + + return Object.assign(p, { + resolve, + reject, + wrap, + }) +} diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index 9632e6990..47121958d 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -2,7 +2,7 @@ import { Readable, finished } from 'stream' import { promisify } from 'util' import { ControlLayer } from 'streamr-client-protocol' -import { wait } from 'streamr-test-utils' +import { wait, waitForCondition } from 'streamr-test-utils' import { uid, fakePrivateKey } from '../utils' import StreamrClient from '../../src' @@ -13,8 +13,6 @@ import config from './config' const { ControlMessage } = ControlLayer -const pFinished = promisify(finished) - const Msg = (opts) => ({ value: uid('msg'), ...opts, @@ -132,7 +130,7 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) }) - it('can kill stream using destroy', async () => { + it('can kill stream using return', async () => { const M = new MessageStream(client) const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) @@ -149,14 +147,46 @@ describe('StreamrClient Stream', () => { for await (const m of sub) { received.push(m) if (received.length === published.length) { - await sub.destroy() + await sub.return() } } - await pFinished(sub) expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) expect(M.count(stream.id)).toBe(0) }) + it('can kill stream using async stop', async () => { + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) + + const published = [] + for (let i = 0; i < 2; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + + const received = [] + let t + try { + for await (const m of sub) { + received.push(m) + // after first message schedule stop + if (received.length === 1) { + // eslint-disable-next-line no-loop-func + t = setTimeout(() => { + sub.stop() + }) + } + } + } finally { + clearTimeout(t) + } + expect(received).toHaveLength(2) + expect(M.count(stream.id)).toBe(0) + }) + it('can subscribe to stream multiple times, get updates then unsubscribe', async () => { const M = new MessageStream(client) const sub1 = await M.subscribe(stream.id) @@ -307,7 +337,7 @@ describe('StreamrClient Stream', () => { } }), ]) - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 2)) + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) expect(received2.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) expect(M.count(stream.id)).toBe(0) }) @@ -327,7 +357,7 @@ describe('StreamrClient Stream', () => { })).rejects.toThrow('abort'), ]) - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 2)) + expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) expect(received2.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) expect(M.count(stream.id)).toBe(0) @@ -406,133 +436,6 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) }) - describe('resends', () => { - it('handles nothing to resend', async () => { - const M = new MessageStream(client) - const sub = await M.resend({ - streamId: stream.id, - last: 5, - }) - - const receivedMsgs = await collect(sub) - expect(receivedMsgs).toHaveLength(0) - expect(M.count(stream.id)).toBe(0) - }) - - describe('with resend data', () => { - let published - beforeEach(async () => { - published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } - await wait(10000) - }, 20000) - - it('can destroy stream', async () => { - const M = new MessageStream(client) - const sub = await M.resend({ - streamId: stream.id, - last: published.length, - }) - - const received = [] - for await (const m of sub) { - received.push(m) - if (received.length === published.length) { - await sub.destroy() - } - } - await pFinished(sub) - }) - - it('requests resend', async () => { - const M = new MessageStream(client) - const sub = await M.resend({ - streamId: stream.id, - last: published.length, - }) - - const receivedMsgs = await collect(sub) - expect(receivedMsgs).toHaveLength(published.length) - expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) - expect(M.count(stream.id)).toBe(0) - }) - - describe('resendSubscribe', () => { - it('can destroy stream', async () => { - const M = new MessageStream(client) - const sub = await M.resendSubscribe({ - streamId: stream.id, - last: published.length, - }) - - expect(M.count(stream.id)).toBe(1) - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - await sub.destroy() - - const received = [] - for await (const m of sub) { - received.push(m) - } - expect(received).toHaveLength(0) - await pFinished(sub) - expect(M.count(stream.id)).toBe(0) - }, 10000) - - it('can request both', async () => { - const M = new MessageStream(client) - const sub = await M.resendSubscribe({ - streamId: stream.id, - last: published.length, - }) - - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - const receivedMsgs = await collect(sub, async ({ received }) => { - if (received.length === published.length + 1) { - await sub.destroy() - } - }) - expect(receivedMsgs).toHaveLength(published.length + 1) - expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.concat(message)) - expect(M.count(stream.id)).toBe(0) - }, 6000) - - it('handles ending prematurely', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) - const M = new MessageStream(client) - const sub = await M.resendSubscribe({ - streamId: stream.id, - last: published.length, - }) - - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - const receivedMsgs = await collect(sub, async ({ received }) => { - if (received.length === 3) { - await sub.return() - expect(unsubscribeEvents).toHaveLength(1) - } - }) - - expect(receivedMsgs).toHaveLength(3) - expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 3)) - expect(M.count(stream.id)).toBe(0) - }, 6000) - }) - }) - }) }) } }) diff --git a/test/integration/StreamResends.test.js b/test/integration/StreamResends.test.js new file mode 100644 index 000000000..cf59756ea --- /dev/null +++ b/test/integration/StreamResends.test.js @@ -0,0 +1,380 @@ +import { ControlLayer } from 'streamr-client-protocol' +import { wait, waitForCondition } from 'streamr-test-utils' +import Debug from 'debug' + +import { uid, fakePrivateKey } from '../utils' +import StreamrClient from '../../src' +import Connection from '../../src/Connection' +import MessageStream from '../../src/Stream' + +import config from './config' + +const { ControlMessage } = ControlLayer + +let ID = 0 +const Msg = (opts) => ({ + value: `msg${ID++}`, // eslint-disable-line no-plusplus + ...opts, +}) + +async function collect(iterator, fn = () => {}) { + const received = [] + for await (const msg of iterator) { + received.push(msg) + await fn({ + msg, iterator, received, + }) + } + + return received +} + +const TEST_REPEATS = 1 + +/* eslint-disable no-await-in-loop */ + +console.log = Debug('Streamr:: CONSOLE ') + +const WAIT_FOR_STORAGE_TIMEOUT = 30000 + +describe('resends', () => { + let expectErrors = 0 // check no errors by default + let onError = jest.fn() + let client + let stream + let published + let emptyStream + + const createClient = (opts = {}) => { + const c = new StreamrClient({ + auth: { + privateKey: fakePrivateKey(), + }, + autoConnect: false, + autoDisconnect: false, + maxRetries: 2, + ...config.clientOptions, + ...opts, + }) + c.onError = jest.fn() + c.on('error', onError) + return c + } + + async function waitForStorage(msg, timeout = 5000) { + const start = Date.now() + let last + // eslint-disable-next-line no-constant-condition + while (true) { + const duration = Date.now() - start + if (duration > timeout) { + client.debug('waitForStorage timeout %o', { + timeout, + duration + }, { + msg, + last: last.map((l) => l.content), + }) + const err = new Error(`timed out after ${duration}ms waiting for message`) + err.msg = msg + throw err + } + + last = await client.getStreamLast({ + streamId: stream.id, + count: 9999, + }) + + let found = false + for (const { content } of last) { + if (content.value === msg.value) { + found = true + break + } + } + + if (found) { + return + } + + client.debug('message not found', { + msg, last: last.map(({ content }) => content) + }) + await wait(500) + } + } + + beforeAll(async () => { + // eslint-disable-next-line require-atomic-updates + client = createClient() + stream = await client.createStream({ + name: uid('stream') + }) + + published = [] + await client.connect() + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + + const lastMessage = published[published.length - 1] + await waitForStorage(lastMessage, WAIT_FOR_STORAGE_TIMEOUT) + await client.disconnect() + }, WAIT_FOR_STORAGE_TIMEOUT * 2) + + beforeEach(async () => { + emptyStream = await client.createStream({ + name: uid('stream') + }) + await client.connect() + expectErrors = 0 + onError = jest.fn() + }) + + afterEach(async () => { + await wait() + // ensure no unexpected errors + expect(onError).toHaveBeenCalledTimes(expectErrors) + if (client) { + expect(client.onError).toHaveBeenCalledTimes(expectErrors) + } + }) + + + afterAll(async () => { + await wait(500) + const last = await client.getStreamLast({ + streamId: stream.id, + count: 9999, + }) + console.log({ + published, + last: last.map((l) => l.content) + }) + if (client) { + client.debug('disconnecting after test') + await client.disconnect() + } + + const openSockets = Connection.getOpen() + if (openSockets !== 0) { + throw new Error(`sockets not closed: ${openSockets}`) + } + }) + + for (let k = 0; k < TEST_REPEATS; k++) { + // eslint-disable-next-line no-loop-func + describe(`test repeat ${k + 1} of ${TEST_REPEATS}`, () => { + describe('no data', () => { + it('handles nothing to resend', async () => { + const M = new MessageStream(client) + const sub = await M.resend({ + streamId: emptyStream.id, + last: 5, + }) + + const receivedMsgs = await collect(sub) + expect(receivedMsgs).toHaveLength(0) + expect(M.count(stream.id)).toBe(0) + }) + + it('resendSubscribe with nothing to resend', async () => { + const M = new MessageStream(client) + const sub = await M.resendSubscribe({ + streamId: emptyStream.id, + last: 5, + }) + + expect(M.count(emptyStream.id)).toBe(1) + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(emptyStream.id, message) + + const received = [] + for await (const m of sub) { + received.push(m) + wait(100) + break + } + + expect(received).toHaveLength(1) + expect(M.count(emptyStream.id)).toBe(0) + }) + }) + + describe('with resend data', () => { + beforeEach(async () => { + // ensure last message is in storage + await waitForStorage(published[published.length - 1], WAIT_FOR_STORAGE_TIMEOUT) + }, WAIT_FOR_STORAGE_TIMEOUT * 1.2) + + it('requests resend', async () => { + const M = new MessageStream(client) + const sub = await M.resend({ + streamId: stream.id, + last: published.length, + }) + + const receivedMsgs = await collect(sub) + expect(receivedMsgs).toHaveLength(published.length) + expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(M.count(stream.id)).toBe(0) + }) + + it('requests resend number', async () => { + const M = new MessageStream(client) + const sub = await M.resend({ + streamId: stream.id, + last: 2, + }) + + const receivedMsgs = await collect(sub) + expect(receivedMsgs).toHaveLength(2) + expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(-2)) + expect(M.count(stream.id)).toBe(0) + }) + + it('closes stream', async () => { + const M = new MessageStream(client) + const sub = await M.resend({ + streamId: stream.id, + last: published.length, + }) + + const received = [] + for await (const m of sub) { + received.push(m) + } + expect(received).toHaveLength(published.length) + expect(M.count(stream.id)).toBe(0) + expect(sub.stream.readable).toBe(false) + expect(sub.stream.writable).toBe(false) + }) + + describe('resendSubscribe', () => { + it('sees resends and realtime', async () => { + const M = new MessageStream(client) + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, + }) + + const message = Msg() + console.log('1 PUBLISHING', message) + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) // should be realtime + published.push(message) + const receivedMsgs = await collect(sub, async ({ received }) => { + if (received.length === published.length) { + await sub.return() + } + }) + + const msgs = receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent()) + expect(msgs).toHaveLength(published.length) + expect(msgs).toEqual(published) + expect(M.count(stream.id)).toBe(0) + expect(sub.realtime.stream.readable).toBe(false) + expect(sub.resend.stream.writable).toBe(false) + }) + + it('can return before start', async () => { + const M = new MessageStream(client) + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, + }) + + expect(M.count(stream.id)).toBe(1) + const message = Msg() + + await sub.return() + console.log('2 PUBLISHING', message) + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + const received = [] + for await (const m of sub) { + received.push(m) + } + + expect(received).toHaveLength(0) + expect(M.count(stream.id)).toBe(0) + expect(sub.realtime.stream.readable).toBe(false) + expect(sub.resend.stream.writable).toBe(false) + }) + + it('can stop asynchronously', async () => { + const M = new MessageStream(client) + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, + }) + + const message = Msg() + console.log('3 PUBLISHING', message) + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + + let t + let receivedMsgs + try { + receivedMsgs = await collect(sub, async ({ received }) => { + if (received.length === published.length) { + t = setTimeout(() => { + sub.stop() + }) + } + }) + } finally { + clearTimeout(t) + } + + const msgs = receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent()) + expect(msgs).toHaveLength(published.length) + expect(msgs).toEqual(published) + expect(M.count(stream.id)).toBe(0) + expect(sub.realtime.stream.readable).toBe(false) + expect(sub.resend.stream.writable).toBe(false) + }) + + it('can end inside resend', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) + const M = new MessageStream(client) + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, + }) + + const message = Msg() + console.log('4 PUBLISHING', message) + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + const END_AFTER = 3 + const receivedMsgs = await collect(sub, async ({ received }) => { + if (received.length === END_AFTER) { + await sub.stop() + expect(unsubscribeEvents).toHaveLength(1) + } + }) + console.log('OUT LOOP', receivedMsgs.length) + const msgs = receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent()) + expect(msgs).toHaveLength(END_AFTER) + expect(msgs).toEqual(published.slice(0, END_AFTER)) + expect(M.count(stream.id)).toBe(0) + expect(sub.realtime.stream.readable).toBe(false) + expect(sub.resend.stream.writable).toBe(false) + }) + }) + }) + }) + } +}) From e95f687397ae442afa25afc76c1ae4f4f639ecc5 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 2 Oct 2020 12:24:18 -0400 Subject: [PATCH 080/517] Support directly supplying partition number when publishing. --- src/Publisher.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Publisher.js b/src/Publisher.js index bf489b0e8..963f5dbd5 100644 --- a/src/Publisher.js +++ b/src/Publisher.js @@ -126,6 +126,10 @@ export class StreamPartitioner { return 0 } + if (typeof partitionKey === 'number') { + return Math.abs(partitionKey) % partitionCount + } + if (!partitionKey) { // Fallback to random partition if no key return Math.floor(Math.random() * partitionCount) From db6c6a52aab84f341461a902a3abbd955bb561b2 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 2 Oct 2020 13:27:53 -0400 Subject: [PATCH 081/517] Clean up StreamResends test. --- test/integration/StreamResends.test.js | 69 +++++++++++++++++--------- 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/test/integration/StreamResends.test.js b/test/integration/StreamResends.test.js index cf59756ea..bf1539e20 100644 --- a/test/integration/StreamResends.test.js +++ b/test/integration/StreamResends.test.js @@ -1,5 +1,5 @@ import { ControlLayer } from 'streamr-client-protocol' -import { wait, waitForCondition } from 'streamr-test-utils' +import { wait } from 'streamr-test-utils' import Debug from 'debug' import { uid, fakePrivateKey } from '../utils' @@ -35,7 +35,7 @@ const TEST_REPEATS = 1 console.log = Debug('Streamr:: CONSOLE ') -const WAIT_FOR_STORAGE_TIMEOUT = 30000 +const WAIT_FOR_STORAGE_TIMEOUT = 6000 describe('resends', () => { let expectErrors = 0 // check no errors by default @@ -61,7 +61,7 @@ describe('resends', () => { return c } - async function waitForStorage(msg, timeout = 5000) { + async function waitForStorage({ streamId, streamPartition = 0, msg, timeout = 5000 }) { const start = Date.now() let last // eslint-disable-next-line no-constant-condition @@ -81,8 +81,9 @@ describe('resends', () => { } last = await client.getStreamLast({ - streamId: stream.id, - count: 9999, + streamId, + streamPartition, + count: 3, }) let found = false @@ -97,7 +98,7 @@ describe('resends', () => { return } - client.debug('message not found', { + client.debug('message not found, retrying... %o', { msg, last: last.map(({ content }) => content) }) await wait(500) @@ -108,7 +109,7 @@ describe('resends', () => { // eslint-disable-next-line require-atomic-updates client = createClient() stream = await client.createStream({ - name: uid('stream') + name: uid('stream'), }) published = [] @@ -121,8 +122,11 @@ describe('resends', () => { } const lastMessage = published[published.length - 1] - await waitForStorage(lastMessage, WAIT_FOR_STORAGE_TIMEOUT) - await client.disconnect() + await waitForStorage({ + msg: lastMessage, + timeout: WAIT_FOR_STORAGE_TIMEOUT, + streamId: stream.id, + }) }, WAIT_FOR_STORAGE_TIMEOUT * 2) beforeEach(async () => { @@ -143,17 +147,8 @@ describe('resends', () => { } }) - afterAll(async () => { await wait(500) - const last = await client.getStreamLast({ - streamId: stream.id, - count: 9999, - }) - console.log({ - published, - last: last.map((l) => l.content) - }) if (client) { client.debug('disconnecting after test') await client.disconnect() @@ -208,7 +203,12 @@ describe('resends', () => { describe('with resend data', () => { beforeEach(async () => { // ensure last message is in storage - await waitForStorage(published[published.length - 1], WAIT_FOR_STORAGE_TIMEOUT) + const lastMessage = published[published.length - 1] + await waitForStorage({ + msg: lastMessage, + timeout: WAIT_FOR_STORAGE_TIMEOUT, + streamId: stream.id, + }) }, WAIT_FOR_STORAGE_TIMEOUT * 1.2) it('requests resend', async () => { @@ -263,7 +263,32 @@ describe('resends', () => { }) const message = Msg() - console.log('1 PUBLISHING', message) + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) // should be realtime + published.push(message) + const receivedMsgs = await collect(sub, async ({ received }) => { + if (received.length === published.length) { + await wait() + await sub.return() + } + }) + + const msgs = receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent()) + expect(msgs).toHaveLength(published.length) + expect(msgs).toEqual(published) + expect(M.count(stream.id)).toBe(0) + expect(sub.realtime.stream.readable).toBe(false) + expect(sub.resend.stream.writable).toBe(false) + }) + + it('sees resends and realtime 2', async () => { + const M = new MessageStream(client) + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, + }) + + const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) // should be realtime published.push(message) @@ -292,7 +317,6 @@ describe('resends', () => { const message = Msg() await sub.return() - console.log('2 PUBLISHING', message) // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) published.push(message) @@ -315,7 +339,6 @@ describe('resends', () => { }) const message = Msg() - console.log('3 PUBLISHING', message) // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) published.push(message) @@ -354,7 +377,6 @@ describe('resends', () => { }) const message = Msg() - console.log('4 PUBLISHING', message) // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) published.push(message) @@ -365,7 +387,6 @@ describe('resends', () => { expect(unsubscribeEvents).toHaveLength(1) } }) - console.log('OUT LOOP', receivedMsgs.length) const msgs = receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent()) expect(msgs).toHaveLength(END_AFTER) expect(msgs).toEqual(published.slice(0, END_AFTER)) From e4977fc1189843320b10393bc9fa34f29cd2d490 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 2 Oct 2020 13:28:54 -0400 Subject: [PATCH 082/517] Add keepAlive Agents for node client. --- src/Connection.js | 7 ++++++- src/rest/authFetch.js | 31 ++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index 75792bf57..34e3adabc 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -3,6 +3,8 @@ import Debug from 'debug' import uniqueId from 'lodash.uniqueid' import WebSocket from 'ws' +import { getAgent } from './rest/authFetch' + // add global support for pretty millisecond formatting with %n Debug.formatters.n = (v) => Debug.humanize(v) @@ -366,7 +368,10 @@ export default class Connection extends EventEmitter { throw new ConnectionError('disconnected before connected') } - const socket = await OpenWebSocket(this.options.url) + const socket = await OpenWebSocket(this.options.url, { + perMessageDeflate: false, + agent: getAgent(this.options.url.startsWith('wss:') ? 'https:' : 'http:'), + }) debug('connected') if (!this.shouldConnect) { await CloseWebSocket(socket) diff --git a/src/rest/authFetch.js b/src/rest/authFetch.js index fdc6cc6e1..07dea48ee 100644 --- a/src/rest/authFetch.js +++ b/src/rest/authFetch.js @@ -1,5 +1,7 @@ import fetch from 'node-fetch' import Debug from 'debug' +import http from 'http' +import https from 'https' import AuthFetchError from '../errors/AuthFetchError' import { getVersionString } from '../utils' @@ -8,6 +10,32 @@ export const DEFAULT_HEADERS = { 'Streamr-Client': `streamr-client-javascript/${getVersionString()}`, } +export function getAgent(protocol) { + /* eslint-disable consistent-return */ + if (process.browser) { + return + } + + if (protocol === 'http:') { + if (!getAgent.httpAgent) { + getAgent.httpAgent = new http.Agent({ + keepAlive: true, + keepAliveMsecs: 10000, + }) + } + return getAgent.httpAgent + } + + if (!getAgent.httpsAgent) { + getAgent.httpsAgent = new https.Agent({ + keepAlive: true, + keepAliveMsecs: 10000, + }) + } + return getAgent.httpsAgent + /* eslint-enable consistent-return */ +} + const debug = Debug('StreamrClient:utils:authfetch') let ID = 0 @@ -22,7 +50,8 @@ export default async function authFetch(url, session, opts, requireNewToken = fa headers: { ...DEFAULT_HEADERS, ...(opts && opts.headers), - } + }, + agent: ({ protocol }) => getAgent(protocol), } // add default 'Content-Type: application/json' header for all POST and PUT requests if (!options.headers['Content-Type'] && (options.method === 'POST' || options.method === 'PUT')) { From e28c8c388a7e496d831b8069470c38d3ea4be5ce Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 2 Oct 2020 15:12:19 -0400 Subject: [PATCH 083/517] Improve stream/iterator cleanup. --- src/Stream.js | 83 ++++++++++++++---------------- test/integration/Stream.test.js | 90 ++++++++++++++++++++++++--------- 2 files changed, 104 insertions(+), 69 deletions(-) diff --git a/src/Stream.js b/src/Stream.js index fb9a145d5..e16a4cec0 100644 --- a/src/Stream.js +++ b/src/Stream.js @@ -6,7 +6,7 @@ import pLimit from 'p-limit' import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import AbortController from 'node-abort-controller' -import { uuid, Defer } from './utils' +import { uuid } from './utils' const pFinished = promisify(finished) @@ -33,31 +33,28 @@ export class AbortError extends Error { function iteratorFinally(iterator, onFinally = () => {}) { let started = false - let finallyRun = false + const onFinallyOnce = pMemoize(onFinally) const g = (async function* It() { started = true try { yield* iterator } finally { - finallyRun = true - await onFinally(iterator) + await onFinallyOnce(iterator) } }()) // overrides return/throw to call onFinally even if generator was never started const oldReturn = g.return g.return = async (...args) => { - if (!started && !finallyRun) { - finallyRun = true - await onFinally(iterator) + if (!started) { + await onFinallyOnce(iterator) } return oldReturn.call(g, ...args) } const oldThrow = g.throw g.throw = async (...args) => { - if (!started && !finallyRun) { - finallyRun = true - await onFinally(iterator) + if (!started) { + await onFinallyOnce(iterator) } return oldThrow.call(g, ...args) } @@ -299,10 +296,9 @@ function SubKey({ streamId, streamPartition = 0 }) { return `${streamId}|${streamPartition}` } -function streamIterator(stream, abortController) { - const it = iteratorFinally((async function* streamIteratorFn() { - yield* stream - }()), async () => { +function streamIterator(stream, { abortController, onFinally = () => {}, }) { + const onFinallyOnce = pMemoize(onFinally) + const endStreamOnce = pMemoize(async () => { stream.destroy() await pFinished(stream, { readable: false, @@ -310,16 +306,20 @@ function streamIterator(stream, abortController) { }) }) + const it = iteratorFinally((async function* streamIteratorFn() { + yield* stream + }()), async () => { + await endStreamOnce(stream) + await onFinallyOnce() + }) + it.stream = stream it.abort = () => abortController && abortController.abort() it.stop = async () => { - stream.destroy() - await pFinished(stream, { - readable: false, - writable: false, - }) + await endStreamOnce(stream) await it.return() } + return it } @@ -360,8 +360,7 @@ class Subscription { async subscribe() { pMemoize.clear(this.sendUnsubscribe) await this.sendSubscribe() - this.initialIterator = this.iterate() - return this + return this.iterate() } async unsubscribe() { @@ -383,8 +382,9 @@ class Subscription { async _cleanup(it) { // if iterator never started, finally block never called, thus need to manually clean it + const hadStream = this.streams.has(it) this.streams.delete(it) - if (!this.streams.size) { + if (hadStream && !this.streams.size) { // unsubscribe if no more streams await this.unsubscribe() } @@ -400,28 +400,18 @@ class Subscription { ...this.options, type: ControlMessage.TYPES.BroadcastMessage, }) - const streamIt = streamIterator(stream) - const it = iteratorFinally(streamIt, async () => { - await this._cleanup(it) + const streamIt = streamIterator(stream, { + abortController: this.abortController, + onFinally: async () => { + await this._cleanup(streamIt) + } }) - it.stream = stream - it.stop = async () => { - await streamIt.stop() - await it.return() - } - this.streams.add(it) + this.streams.add(streamIt) - return it + return streamIt } [Symbol.asyncIterator]() { - if (this.initialIterator) { - const it = this.initialIterator - delete this.initialIterator - if (this.streams.has(it)) { - return it - } - } return this.iterate() } } @@ -449,12 +439,17 @@ export default class Subscriptions { async resend(opts) { const options = validateOptions(opts) + const abortController = new AbortController() const stream = messageStream(this.client, { - ...options, + signal: abortController.signal, type: ControlMessage.TYPES.UnicastMessage, + ...options, + }) + + const streamIt = streamIterator(stream, { + abortController, }) - const it = streamIterator(stream) const requestId = uuid('rs') // eslint-disable-next-line promise/catch-or-return const onResendDone = waitForResponse({ @@ -470,7 +465,7 @@ export default class Subscriptions { } return v }, (err) => { - return it.stop(err) + return streamIt.stop(err) }) await Promise.race([ @@ -481,11 +476,11 @@ export default class Subscriptions { onResendDone ]) - return it + return streamIt } async resendSubscribe(options) { - const sub = (await this.subscribe(options))[Symbol.asyncIterator]() + const sub = await this.subscribe(options) const resendSub = await this.resend(options) const stop = async () => { await Promise.all([ diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index 47121958d..dec6e8b2f 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -1,8 +1,5 @@ -import { Readable, finished } from 'stream' -import { promisify } from 'util' - import { ControlLayer } from 'streamr-client-protocol' -import { wait, waitForCondition } from 'streamr-test-utils' +import { wait } from 'streamr-test-utils' import { uid, fakePrivateKey } from '../utils' import StreamrClient from '../../src' @@ -123,31 +120,33 @@ describe('StreamrClient Stream', () => { for await (const m of sub) { received.push(m) if (received.length === published.length) { - await sub.return() + return } } expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) expect(M.count(stream.id)).toBe(0) }) - it('can kill stream using return', async () => { + it('subscribes immediately', async () => { const M = new MessageStream(client) const sub = await M.subscribe(stream.id) - expect(M.count(stream.id)).toBe(1) + expect(M.count(stream.id)).toBe(1) const published = [] for (let i = 0; i < 5; i++) { const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) published.push(message) + // eslint-disable-next-line no-await-in-loop + await wait(100) } const received = [] for await (const m of sub) { received.push(m) if (received.length === published.length) { - await sub.return() + return } } expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) @@ -167,8 +166,9 @@ describe('StreamrClient Stream', () => { published.push(message) } - const received = [] let t + let expectedLength + const received = [] try { for await (const m of sub) { received.push(m) @@ -176,6 +176,8 @@ describe('StreamrClient Stream', () => { if (received.length === 1) { // eslint-disable-next-line no-loop-func t = setTimeout(() => { + expectedLength = received.length + // should not see any more messages after stop sub.stop() }) } @@ -183,7 +185,8 @@ describe('StreamrClient Stream', () => { } finally { clearTimeout(t) } - expect(received).toHaveLength(2) + // gets some messages but not all + expect(received).toHaveLength(expectedLength) expect(M.count(stream.id)).toBe(0) }) @@ -199,19 +202,21 @@ describe('StreamrClient Stream', () => { // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) published.push(message) + // eslint-disable-next-line no-await-in-loop + await wait(100) } const [received1, received2] = await Promise.all([ - collect(sub1, async ({ received, iterator }) => { + collect(sub1, ({ received, iterator }) => { if (received.length === published.length) { - await iterator.return() + iterator.return() } }), - collect(sub2, async ({ received, iterator }) => { + collect(sub2, ({ received, iterator }) => { if (received.length === published.length) { - await iterator.return() + iterator.return() } - }), + }) ]) expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) @@ -253,7 +258,7 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) }) - it('can subscribe to stream and get some updates then unsubscribe mid-stream', async () => { + it('can subscribe to stream and get some updates then unsubscribe mid-stream with stop', async () => { const M = new MessageStream(client) const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) @@ -266,13 +271,15 @@ describe('StreamrClient Stream', () => { published.push(message) } - const receivedMsgs = await collect(sub, async ({ received, iterator }) => { + const received = [] + for await (const m of sub) { + received.push(m) if (received.length === 1) { - await iterator.return() + await sub.stop() } - }) + } - expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) expect(M.count(stream.id)).toBe(0) }) @@ -292,16 +299,50 @@ describe('StreamrClient Stream', () => { await client.publish(stream.id, message) published.push(message) } - const receivedMsgs = await collect(sub, async ({ received }) => { + const received = [] + for await (const m of sub) { + received.push(m) if (received.length === 2) { expect(unsubscribeEvents).toHaveLength(0) - await sub.return() + await sub.stop() expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) } + } + expect(received).toHaveLength(2) + expect(M.count(stream.id)).toBe(0) + }) + + it('can stop + return and it will wait for unsubscribe', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) }) - expect(receivedMsgs).toHaveLength(2) + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + const received = [] + for await (const m of sub) { + received.push(m) + if (received.length === 2) { + expect(unsubscribeEvents).toHaveLength(0) + await Promise.race([ + sub.return(), + sub.stop(), + ]) + expect(unsubscribeEvents).toHaveLength(1) + expect(M.count(stream.id)).toBe(0) + } + } + expect(received).toHaveLength(2) expect(M.count(stream.id)).toBe(0) }) }) @@ -328,7 +369,7 @@ describe('StreamrClient Stream', () => { } }) - it('can subscribe to stream multiple times then unsubscribe mid-stream', async () => { + it('can subscribe to stream multiple times then unsubscribe all mid-stream', async () => { const [received1, received2] = await Promise.all([ collect(sub1), collect(sub2, async ({ received }) => { @@ -435,7 +476,6 @@ describe('StreamrClient Stream', () => { expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) }) - }) } }) From e9e68c0b2fd64e54a3f5b1d9793b3e8692493f9d Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 2 Oct 2020 15:48:53 -0400 Subject: [PATCH 084/517] Rename stop to end. --- src/Stream.js | 28 +++++++----- test/integration/Stream.test.js | 62 +++++++++++++++++++++----- test/integration/StreamResends.test.js | 12 +++-- 3 files changed, 76 insertions(+), 26 deletions(-) diff --git a/src/Stream.js b/src/Stream.js index e16a4cec0..1585d1727 100644 --- a/src/Stream.js +++ b/src/Stream.js @@ -315,7 +315,7 @@ function streamIterator(stream, { abortController, onFinally = () => {}, }) { it.stream = stream it.abort = () => abortController && abortController.abort() - it.stop = async () => { + it.end = async () => { await endStreamOnce(stream) await it.return() } @@ -323,6 +323,11 @@ function streamIterator(stream, { abortController, onFinally = () => {}, }) { return it } +/** + * Manages creating iterators for a streamr stream. + * When all iterators are done, calls unsubscribe. + */ + class Subscription { constructor(client, options) { this.client = client @@ -359,8 +364,9 @@ class Subscription { async subscribe() { pMemoize.clear(this.sendUnsubscribe) + const iterator = this.iterate() // start iterator immediately await this.sendSubscribe() - return this.iterate() + return iterator } async unsubscribe() { @@ -368,9 +374,9 @@ class Subscription { await this.sendUnsubscribe() } - async stop() { + async end() { await Promise.all([...this.streams].map(async (it) => { - await it.stop() + await it.end() })) } @@ -465,7 +471,7 @@ export default class Subscriptions { } return v }, (err) => { - return streamIt.stop(err) + return streamIt.end(err) }) await Promise.race([ @@ -482,21 +488,21 @@ export default class Subscriptions { async resendSubscribe(options) { const sub = await this.subscribe(options) const resendSub = await this.resend(options) - const stop = async () => { + const end = async () => { await Promise.all([ - sub.stop(), - resendSub.stop(), + sub.end(), + resendSub.end(), ]) } const it = iteratorFinally((async function* ResendSubIterator() { yield* resendSub yield* sub - }()), stop) + }()), end) it.abort = () => this.abortController && this.abortController.abort() - it.stop = async () => { - await stop() + it.end = async () => { + await end() return it.return() } diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index dec6e8b2f..2d4807a4d 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -153,7 +153,7 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) }) - it('can kill stream using async stop', async () => { + it('can kill stream using async end', async () => { const M = new MessageStream(client) const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) @@ -172,13 +172,13 @@ describe('StreamrClient Stream', () => { try { for await (const m of sub) { received.push(m) - // after first message schedule stop + // after first message schedule end if (received.length === 1) { // eslint-disable-next-line no-loop-func t = setTimeout(() => { expectedLength = received.length - // should not see any more messages after stop - sub.stop() + // should not see any more messages after end + sub.end() }) } } @@ -258,7 +258,7 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) }) - it('can subscribe to stream and get some updates then unsubscribe mid-stream with stop', async () => { + it('can subscribe to stream and get some updates then unsubscribe mid-stream with end', async () => { const M = new MessageStream(client) const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) @@ -275,7 +275,7 @@ describe('StreamrClient Stream', () => { for await (const m of sub) { received.push(m) if (received.length === 1) { - await sub.stop() + await sub.end() } } @@ -304,7 +304,7 @@ describe('StreamrClient Stream', () => { received.push(m) if (received.length === 2) { expect(unsubscribeEvents).toHaveLength(0) - await sub.stop() + await sub.end() expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) } @@ -313,7 +313,7 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) }) - it('can stop + return and it will wait for unsubscribe', async () => { + it('can end + return and it will wait for unsubscribe', async () => { const unsubscribeEvents = [] client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { unsubscribeEvents.push(m) @@ -334,10 +334,50 @@ describe('StreamrClient Stream', () => { received.push(m) if (received.length === 2) { expect(unsubscribeEvents).toHaveLength(0) - await Promise.race([ + const tasks = [ sub.return(), - sub.stop(), - ]) + sub.end(), + ] + await Promise.race(tasks) + expect(unsubscribeEvents).toHaveLength(1) + await Promise.all(tasks) + expect(unsubscribeEvents).toHaveLength(1) + expect(M.count(stream.id)).toBe(0) + } + } + expect(received).toHaveLength(2) + expect(M.count(stream.id)).toBe(0) + }) + + it('can end + multiple times and it will wait for unsubscribe', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) + + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + const received = [] + for await (const m of sub) { + received.push(m) + if (received.length === 2) { + expect(unsubscribeEvents).toHaveLength(0) + const tasks = [ + sub.end(), + sub.end(), + sub.end(), + ] + await Promise.race(tasks) + expect(unsubscribeEvents).toHaveLength(1) + await Promise.all(tasks) expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) } diff --git a/test/integration/StreamResends.test.js b/test/integration/StreamResends.test.js index bf1539e20..f956e2b36 100644 --- a/test/integration/StreamResends.test.js +++ b/test/integration/StreamResends.test.js @@ -278,10 +278,12 @@ describe('resends', () => { expect(msgs).toEqual(published) expect(M.count(stream.id)).toBe(0) expect(sub.realtime.stream.readable).toBe(false) + expect(sub.realtime.stream.writable).toBe(false) + expect(sub.resend.stream.readable).toBe(false) expect(sub.resend.stream.writable).toBe(false) }) - it('sees resends and realtime 2', async () => { + it('sees resends and realtime again', async () => { const M = new MessageStream(client) const sub = await M.resendSubscribe({ streamId: stream.id, @@ -303,6 +305,8 @@ describe('resends', () => { expect(msgs).toEqual(published) expect(M.count(stream.id)).toBe(0) expect(sub.realtime.stream.readable).toBe(false) + expect(sub.realtime.stream.writable).toBe(false) + expect(sub.resend.stream.readable).toBe(false) expect(sub.resend.stream.writable).toBe(false) }) @@ -331,7 +335,7 @@ describe('resends', () => { expect(sub.resend.stream.writable).toBe(false) }) - it('can stop asynchronously', async () => { + it('can end asynchronously', async () => { const M = new MessageStream(client) const sub = await M.resendSubscribe({ streamId: stream.id, @@ -349,7 +353,7 @@ describe('resends', () => { receivedMsgs = await collect(sub, async ({ received }) => { if (received.length === published.length) { t = setTimeout(() => { - sub.stop() + sub.end() }) } }) @@ -383,7 +387,7 @@ describe('resends', () => { const END_AFTER = 3 const receivedMsgs = await collect(sub, async ({ received }) => { if (received.length === END_AFTER) { - await sub.stop() + await sub.end() expect(unsubscribeEvents).toHaveLength(1) } }) From fac8c440fbec41f36cf317bdcee46eb70ad203fe Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 2 Oct 2020 16:57:07 -0400 Subject: [PATCH 085/517] Clean up, add comments, reorganise, better error handling. --- src/Stream.js | 430 +++++++++++++++++++------------- test/integration/Stream.test.js | 34 +++ 2 files changed, 291 insertions(+), 173 deletions(-) diff --git a/src/Stream.js b/src/Stream.js index 1585d1727..46339c66b 100644 --- a/src/Stream.js +++ b/src/Stream.js @@ -26,6 +26,23 @@ export class AbortError extends Error { } } +/** + * Convert allSettled results into a thrown Aggregate error if necessary. + */ + +async function allSettledValues(items, errorMessage) { + const result = await Promise.allSettled(items) + + const errs = result.filter(({ status }) => status === 'rejected').map(({ reason }) => reason) + if (errs.length) { + const err = new Error([errorMessage, ...errs].filter(Boolean).join('\n')) + err.errors = errs + throw err + } + + return result.map(({ value }) => value) +} + /** * Allows injecting a function to execute after an iterator finishes. * Executes finally function even if generator not started. @@ -39,26 +56,147 @@ function iteratorFinally(iterator, onFinally = () => {}) { try { yield* iterator } finally { - await onFinallyOnce(iterator) + await onFinallyOnce() } }()) // overrides return/throw to call onFinally even if generator was never started const oldReturn = g.return - g.return = async (...args) => { - if (!started) { - await onFinallyOnce(iterator) - } - return oldReturn.call(g, ...args) - } const oldThrow = g.throw - g.throw = async (...args) => { - if (!started) { - await onFinallyOnce(iterator) + return Object.assign(g, { + return: async (...args) => { + if (!started) { + await onFinallyOnce(iterator) + } + return oldReturn.call(g, ...args) + }, + throw: async (...args) => { + if (!started) { + await onFinallyOnce() + } + return oldThrow.call(g, ...args) + }, + }) +} + +/** + * Iterates over a Stream + * Cleans up stream/stops iterator if either stream or iterator ends. + * Adds abort + end methods to iterator + */ + +function streamIterator(stream, { abortController, onFinally = () => {}, }) { + const onFinallyOnce = pMemoize(onFinally) // called once when stream ends + const endStreamOnce = pMemoize(async (optionalErr) => { + // ends stream + waits for end + stream.destroy(optionalErr) + await pFinished(stream, { + // necessary or can get premature close errors + // TODO: figure out why + readable: false, + writable: false, + }) + }) + + const it = iteratorFinally((async function* streamIteratorFn() { + yield* stream + }()), async () => { + await endStreamOnce() + await onFinallyOnce() + }) + + return Object.assign(it, { + stream, + async abort() { + if (abortController) { + abortController.abort() + } else { + await it.end(new AbortError()) + } + }, + async end(optionalErr) { + await endStreamOnce(optionalErr) + + if (optionalErr) { + await it.throw(optionalErr) + return + } + + await it.return() } - return oldThrow.call(g, ...args) + }) +} + +function getIsMatchingStreamMessage({ streamId, streamPartition = 0 }) { + return function isMatchingStreamMessage({ streamMessage }) { + const msgStreamId = streamMessage.getStreamId() + if (streamId !== msgStreamId) { return false } + const msgPartition = streamMessage.getStreamPartition() + if (streamPartition !== msgPartition) { return false } + return true + } +} + +/** + * Listen for matching stream messages on connection. + * Write messages into a Stream. + */ + +function messageStream(connection, { streamId, streamPartition, signal, type = ControlMessage.TYPES.BroadcastMessage }) { + if (signal && signal.aborted) { + throw new AbortError() + } + + // stream acts as buffer + const msgStream = new PassThrough({ + objectMode: true, + }) + + const onAbort = () => { + return msgStream.destroy(new AbortError()) + } + + if (signal) { + signal.addEventListener('abort', onAbort, { + once: true + }) } - return g + + const isMatchingStreamMessage = getIsMatchingStreamMessage({ + streamId, + streamPartition + }) + + // write matching messages to stream + const onMessage = (msg) => { + if (!isMatchingStreamMessage(msg)) { return } + msgStream.write(msg) + } + + connection.on(type, onMessage) + + // remove onMessage handler & clean up as soon as we see any 'end' events + const onClose = () => { + connection.off(type, onMessage) + // clean up abort signal + if (signal) { + signal.removeEventListener('abort', onAbort, { + once: true, + }) + } + // clean up other handlers + msgStream + .off('close', onClose) + .off('end', onClose) + .off('destroy', onClose) + .off('finish', onClose) + } + + return msgStream + .once('close', onClose) + .once('end', onClose) + .once('destroy', onClose) + .once('finish', onClose) } export function validateOptions(optionsOrStreamId) { @@ -100,6 +238,10 @@ const PAIRS = new Map([ [ControlMessage.TYPES.ResendRangeRequest, ResendResponses], ]) +/** + * Wait for matching response types to requestId, or ErrorResponse. + */ + async function waitForResponse({ connection, types, requestId }) { return new Promise((resolve, reject) => { let cleanup @@ -141,15 +283,9 @@ async function waitForRequestResponse(client, request) { }) } -function getIsMatchingStreamMessage({ streamId, streamPartition = 0 }) { - return function isMatchingStreamMessage({ streamMessage }) { - const msgStreamId = streamMessage.getStreamId() - if (streamId !== msgStreamId) { return false } - const msgPartition = streamMessage.getStreamPartition() - if (streamPartition !== msgPartition) { return false } - return true - } -} +// +// Subscribe/Unsubscribe +// async function subscribe(client, { streamId, streamPartition = 0 }) { const sessionToken = await client.session.getSessionToken() @@ -181,6 +317,10 @@ async function unsubscribe(client, { streamId, streamPartition = 0 }) { return onResponse } +// +// Resends +// + function createResendRequest({ requestId = uuid('rs'), streamId, @@ -239,88 +379,47 @@ async function resend(client, options) { return onResponse } -function messageStream(client, { streamId, streamPartition, signal, type = ControlMessage.TYPES.BroadcastMessage }) { - if (signal && signal.aborted) { - throw new AbortError() - } - - const queue = new PassThrough({ - objectMode: true, +async function iterateResend(client, opts) { + const options = validateOptions(opts) + const abortController = new AbortController() + const stream = messageStream(client.connection, { + signal: abortController.signal, + type: ControlMessage.TYPES.UnicastMessage, + ...options, }) - const onAbort = () => { - return queue.destroy(new AbortError()) - } - - if (signal) { - signal.addEventListener('abort', onAbort, { - once: true - }) - } - - const isMatchingStreamMessage = getIsMatchingStreamMessage({ - streamId, - streamPartition + const streamIt = streamIterator(stream, { + abortController, }) - const onMessage = (msg) => { - if (isMatchingStreamMessage(msg)) { - queue.write(msg) - } - } + const requestId = uuid('rs') - client.connection.on(type, onMessage) - const onClose = () => { - queue.off('close', onClose) - queue.off('end', onClose) - queue.off('destroy', onClose) - queue.off('finish', onClose) - client.connection.off(type, onMessage) - if (signal) { - signal.removeEventListener('abort', onAbort, { - once: true, - }) + // wait for resend complete message(s) + const onResendDone = waitForResponse({ // eslint-disable-line promise/catch-or-return + requestId, + connection: client.connection, + types: [ + ControlMessage.TYPES.ResendResponseResent, + ControlMessage.TYPES.ResendResponseNoResend, + ], + }).then((v) => { + if (stream.writable) { + stream.end() } - } - - queue.once('close', onClose) - queue.once('end', onClose) - queue.once('destroy', onClose) - queue.once('finish', onClose) - - return queue -} - -function SubKey({ streamId, streamPartition = 0 }) { - if (streamId == null) { throw new Error(`SubKey: invalid streamId: ${streamId} ${streamPartition}`) } - return `${streamId}|${streamPartition}` -} - -function streamIterator(stream, { abortController, onFinally = () => {}, }) { - const onFinallyOnce = pMemoize(onFinally) - const endStreamOnce = pMemoize(async () => { - stream.destroy() - await pFinished(stream, { - readable: false, - writable: false, - }) + return v + }, (err) => { + return streamIt.end(err) }) - const it = iteratorFinally((async function* streamIteratorFn() { - yield* stream - }()), async () => { - await endStreamOnce(stream) - await onFinallyOnce() - }) - - it.stream = stream - it.abort = () => abortController && abortController.abort() - it.end = async () => { - await endStreamOnce(stream) - await it.return() - } + // wait for resend complete message or resend request done + await Promise.race([ + resend(client, { + requestId, ...options, + }), + onResendDone + ]) - return it + return streamIt } /** @@ -374,16 +473,16 @@ class Subscription { await this.sendUnsubscribe() } - async end() { - await Promise.all([...this.streams].map(async (it) => { - await it.end() - })) + async end(optionalErr) { + await allSettledValues([...this.streams].map(async (it) => { + await it.end(optionalErr) + }), 'end failed') } async return() { - await Promise.all([...this.streams].map(async (it) => { + await allSettledValues([...this.streams].map(async (it) => { await it.return() - })) + }), 'return failed') } async _cleanup(it) { @@ -401,17 +500,19 @@ class Subscription { } iterate() { - const stream = messageStream(this.client, { - signal: this.abortController.signal, + const stream = messageStream(this.client.connection, { ...this.options, + signal: this.abortController.signal, type: ControlMessage.TYPES.BroadcastMessage, }) + const streamIt = streamIterator(stream, { abortController: this.abortController, onFinally: async () => { await this._cleanup(streamIt) } }) + this.streams.add(streamIt) return streamIt @@ -422,6 +523,15 @@ class Subscription { } } +function SubKey({ streamId, streamPartition = 0 }) { + if (streamId == null) { throw new Error(`SubKey: invalid streamId: ${streamId} ${streamPartition}`) } + return `${streamId}::${streamPartition}` +} + +/** + * Top-level interface for creating/destroying subscriptions. + */ + export default class Subscriptions { constructor(client) { this.client = client @@ -443,72 +553,17 @@ export default class Subscriptions { return sub ? sub.count() : 0 } - async resend(opts) { - const options = validateOptions(opts) - const abortController = new AbortController() - const stream = messageStream(this.client, { - signal: abortController.signal, - type: ControlMessage.TYPES.UnicastMessage, - ...options, - }) - - const streamIt = streamIterator(stream, { - abortController, - }) - - const requestId = uuid('rs') - // eslint-disable-next-line promise/catch-or-return - const onResendDone = waitForResponse({ - connection: this.client.connection, - types: [ - ControlMessage.TYPES.ResendResponseResent, - ControlMessage.TYPES.ResendResponseNoResend, - ], - requestId, - }).then((v) => { - if (stream.writable) { - stream.end() - } - return v - }, (err) => { - return streamIt.end(err) - }) - - await Promise.race([ - resend(this.client, { - requestId, - ...options, - }), - onResendDone - ]) - - return streamIt - } - - async resendSubscribe(options) { - const sub = await this.subscribe(options) - const resendSub = await this.resend(options) - const end = async () => { - await Promise.all([ - sub.end(), - resendSub.end(), - ]) - } - - const it = iteratorFinally((async function* ResendSubIterator() { - yield* resendSub - yield* sub - }()), end) + async unsubscribe(options) { + const key = SubKey(validateOptions(options)) + const sub = this.subscriptions.get(key) + if (!sub) { return } - it.abort = () => this.abortController && this.abortController.abort() - it.end = async () => { - await end() - return it.return() + // wait for any outstanding operations + if (sub.hasPending()) { + await sub.queue(() => {}) } - it.resend = resendSub - it.realtime = sub - return it + await sub.return() // close all streams (thus unsubscribe) } async subscribe(options) { @@ -521,16 +576,45 @@ export default class Subscriptions { return sub.subscribe() } - async unsubscribe(options) { - const key = SubKey(validateOptions(options)) - const sub = this.subscriptions.get(key) - if (!sub) { return } + async resend(opts) { + return iterateResend(this.client, opts) + } - // wait for any outstanding operations - if (sub.hasPending()) { - await sub.queue(() => {}) + async resendSubscribe(options) { + // create realtime subscription + const sub = await this.subscribe(options) + // create resend + const resendSub = await this.resend(options) + + // end both on end + async function end(optionalErr) { + await allSettledValues([ + sub.end(optionalErr), + resendSub.end(optionalErr), + ], 'resend end failed') } - await sub.return() // close all streams (thus unsubscribe) + const it = iteratorFinally((async function* ResendSubIterator() { + // iterate over resend + yield* resendSub + // then iterate over realtime subscription + yield* sub + }()), () => end()) + + // attach additional utility functions + return Object.assign(it, { + realtime: sub, + resend: resendSub, + abort: () => ( + this.abortController && this.abortController.abort() + ), + async end(optionalErr) { // eslint-disable-line require-atomic-updates + try { + await end(optionalErr) + } finally { + await it.return() + } + } + }) } } diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index 2d4807a4d..d7b6dfbb2 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -190,6 +190,40 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) }) + it('can kill stream with throw', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) + + const published = [] + for (let i = 0; i < 2; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + + const err = new Error('expected error') + const received = [] + await expect(async () => { + for await (const m of sub) { + received.push(m) + // after first message schedule end + if (received.length) { + throw err + } + } + }).rejects.toThrow(err) + // gets some messages but not all + expect(received).toHaveLength(1) + expect(unsubscribeEvents).toHaveLength(1) + expect(M.count(stream.id)).toBe(0) + }) + it('can subscribe to stream multiple times, get updates then unsubscribe', async () => { const M = new MessageStream(client) const sub1 = await M.subscribe(stream.id) From 4ba2cc050be76b396dc569bf1afa256a4bae440c Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 6 Oct 2020 14:37:44 -0400 Subject: [PATCH 086/517] Add src/iterators so can unit test. --- src/iterators.js | 174 ++++++++++++++++++++++++++++++++++++ test/unit/iterators.test.js | 133 +++++++++++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100644 src/iterators.js create mode 100644 test/unit/iterators.test.js diff --git a/src/iterators.js b/src/iterators.js new file mode 100644 index 000000000..e79b1c310 --- /dev/null +++ b/src/iterators.js @@ -0,0 +1,174 @@ +import { PassThrough, finished } from 'stream' +import { promisify } from 'util' + +import pMemoize from 'p-memoize' + +export class AbortError extends Error { + constructor(msg = '', ...args) { + super(`The operation was aborted. ${msg}`, ...args) + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor) + } + } +} +const pFinished = promisify(finished) + +export async function endStream(stream, optionalErr) { + // ends stream + waits for end + stream.destroy(optionalErr) + await true + try { + await pFinished(stream) + } catch (err) { + if (err.code === 'ERR_STREAM_PREMATURE_CLOSE') { + await pFinished(stream) + } + } +} + +/** + * Allows injecting a function to execute after an iterator finishes. + * Executes finally function even if generator not started. + */ +const isIteratorFinally = Symbol('iteratorFinally') +export function iteratorFinally(iterator, onFinally = () => {}) { + let started = false + const onFinallyOnce = pMemoize(onFinally) + const g = (async function* It() { + started = true + try { + yield* iterator + } finally { + await onFinallyOnce() + } + }()) + g[isIteratorFinally] = true + + // overrides return/throw to call onFinally even if generator was never started + const oldReturn = g.return + const oldThrow = g.throw + return Object.assign(g, { + return: async (...args) => { + if (!started) { + await onFinallyOnce(iterator) + } + return oldReturn.call(g, ...args) + }, + throw: async (...args) => { + if (!started) { + await onFinallyOnce() + } + return oldThrow.call(g, ...args) + }, + }) +} + +const isCancelableIterator = Symbol('CancelableIterator') +export function CancelableIterator(iterable) { + let cancel + const onCancel = new Promise((resolve, reject) => { + cancel = (value) => { + if (value instanceof Error) { + reject(value) + return + } + resolve(value) + } + }) + + const cancelableIterator = iteratorFinally((async function* Gen() { + const it = iterable[Symbol.asyncIterator]() + while (true) { + // eslint-disable-next-line no-await-in-loop + const { value, done } = await Promise.race([ + it.next(), + onCancel, + ]) + if (done) { + return value + } + yield value + } + }()), () => { + cancel({ + done: true, + value: undefined, + }) + }) + + return Object.assign(cancelableIterator, { + [isCancelableIterator]: true, + onCancel, + cancel, + }) +} +CancelableIterator.is = (it) => it[isCancelableIterator] + +export function pipeline(...iterables) { + const iterators = new Set() + function done(err) { + const its = new Set(iterators) + iterators.clear() + its.forEach((it) => { + it.cancel(err) + }) + } + + return iterables.reduce((prev, next) => { + const it = CancelableIterator(async function* Gen() { + try { + const nextIterable = typeof next === 'function' ? next(prev) : next + yield* nextIterable + } catch (err) { + done(err) + } + }()) + iterators.add(it) + return it + }, undefined) +} + +/** + * Iterates over a Stream + * Cleans up stream/stops iterator if either stream or iterator ends. + * Adds abort + end methods to iterator + */ + +const isStreamIterator = Symbol('StreamIterator') + +export function StreamIterator(stream, { abortController, onFinally = () => {}, } = {}) { + const onFinallyOnce = pMemoize(onFinally) // called once when stream ends + + const it = iteratorFinally((async function* StreamIteratorFn() { + yield* stream + }()), async () => { + try { + await endStream(stream) + } finally { + await onFinallyOnce() + } + }) + + return Object.assign(it, { + [isStreamIterator]: true, + stream, + async abort() { + if (abortController) { + abortController.abort() + } else { + await it.end(new AbortError()) + } + }, + async end(optionalErr) { + try { + await endStream(stream, optionalErr) + } finally { + if (optionalErr) { + await it.throw(optionalErr) + } else { + await it.return() + } + } + } + }) +} diff --git a/test/unit/iterators.test.js b/test/unit/iterators.test.js new file mode 100644 index 000000000..302962368 --- /dev/null +++ b/test/unit/iterators.test.js @@ -0,0 +1,133 @@ +import { Readable } from 'stream' + +import { endStream, iteratorFinally, StreamIterator, CancelableIterator, pipeline, AbortError } from '../../src/iterators' + +const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) + +const expected = [1, 2, 3] + +async function* generate(items = expected) { + await wait(5) + for await (const item of items) { + await wait(5) + yield item + await wait(5) + } + await wait(5) +} + +const WAIT = 50 + +describe('Iterator Utils', () => { + describe('iteratorFinally', () => { + let onFinally + let onFinallyAfter + const MAX_ITEMS = 2 + + beforeEach(() => { + onFinallyAfter = jest.fn() + onFinally = jest.fn(async () => { + await wait(WAIT) + onFinallyAfter() + }) + }) + + afterEach(() => { + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) + }) + + it('runs fn when iterator complete', async () => { + const received = [] + for await (const msg of iteratorFinally(generate(), onFinally)) { + received.push(msg) + } + expect(received).toEqual(expected) + }) + + it('runs fn when iterator returns during iteration', async () => { + const received = [] + for await (const msg of iteratorFinally(generate(), onFinally)) { + received.push(msg) + if (received.length === MAX_ITEMS) { + return + } + } + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + }) + + it('runs fn when iterator throws during iteration', async () => { + const received = [] + const err = new Error('expected err') + await expect(async () => { + for await (const msg of iteratorFinally(generate(), onFinally)) { + received.push(msg) + if (received.length === MAX_ITEMS) { + throw err + } + } + }).rejects.toThrow(err) + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + }) + + it('runs fn when iterator returns + throws during iteration', async () => { + const received = [] + const err = new Error('expected err') + const it = iteratorFinally(generate(), onFinally) + await expect(async () => { + for await (const msg of it) { + received.push(msg) + if (received.length === MAX_ITEMS) { + it.return() + throw err + } + } + }).rejects.toThrow(err) + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + }) + + it('runs fn when iterator returns before iteration', async () => { + const received = [] + const it = iteratorFinally(generate(), onFinally) + await it.return() + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) + for await (const msg of it) { + received.push(msg) + } + expect(received).toEqual([]) + }) + + it('runs fn when iterator throws before iteration', async () => { + const received = [] + const err = new Error('expected err') + const it = iteratorFinally(generate(), onFinally) + await expect(async () => it.throw(err)).rejects.toThrow(err) + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) + // doesn't throw, matches native iterators + for await (const msg of it) { + received.push(msg) + } + expect(received).toEqual([]) + }) + + it('runs fn once', async () => { + const received = [] + const it = iteratorFinally(generate(), onFinally) + + for await (const msg of it) { + received.push(msg) + if (received.length === MAX_ITEMS) { + await Promise.all([ + it.return(), + it.return(), + ]) + return + } + } + expect(received).toEqual([]) + }) + }) +}) + From 9d2f5e07c9292e2e59505ca69ef6dcfb926d7616 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 6 Oct 2020 17:50:00 -0400 Subject: [PATCH 087/517] Test iterator functions. Implement pipeline + cancel. --- src/iterators.js | 110 ++++++--- test/unit/iterators.test.js | 460 ++++++++++++++++++++++++++++++++++-- 2 files changed, 508 insertions(+), 62 deletions(-) diff --git a/src/iterators.js b/src/iterators.js index e79b1c310..1bc8c541c 100644 --- a/src/iterators.js +++ b/src/iterators.js @@ -1,4 +1,4 @@ -import { PassThrough, finished } from 'stream' +import { finished } from 'stream' import { promisify } from 'util' import pMemoize from 'p-memoize' @@ -30,7 +30,7 @@ export async function endStream(stream, optionalErr) { * Allows injecting a function to execute after an iterator finishes. * Executes finally function even if generator not started. */ -const isIteratorFinally = Symbol('iteratorFinally') + export function iteratorFinally(iterator, onFinally = () => {}) { let started = false const onFinallyOnce = pMemoize(onFinally) @@ -42,7 +42,6 @@ export function iteratorFinally(iterator, onFinally = () => {}) { await onFinallyOnce() } }()) - g[isIteratorFinally] = true // overrides return/throw to call onFinally even if generator was never started const oldReturn = g.return @@ -63,69 +62,111 @@ export function iteratorFinally(iterator, onFinally = () => {}) { }) } -const isCancelableIterator = Symbol('CancelableIterator') -export function CancelableIterator(iterable) { +export function CancelableIterator(iterable, onFinally = () => {}) { let cancel + let cancelled = false + let cancelableIterator + let error + let waiting = false + const onCancel = new Promise((resolve, reject) => { cancel = (value) => { + if (cancelled) { return } + + cancelled = true + if (value instanceof Error) { - reject(value) + error = value + if (!waiting) { + cancelableIterator.throw(error) + return + } + reject(error) + return + } + + if (!waiting) { + cancelableIterator.return() return } - resolve(value) + + try { + resolve({ + value, + done: true, + }) + } catch (err) { + reject(err) + } } + }).finally(() => { + cancel = () => onCancel }) - const cancelableIterator = iteratorFinally((async function* Gen() { - const it = iterable[Symbol.asyncIterator]() + let innerIterator + cancelableIterator = iteratorFinally((async function* Gen() { + innerIterator = iterable[Symbol.asyncIterator]() while (true) { - // eslint-disable-next-line no-await-in-loop - const { value, done } = await Promise.race([ - it.next(), - onCancel, - ]) + waiting = true + let value + let done + try { + // eslint-disable-next-line no-await-in-loop + ({ value, done } = await Promise.race([ + innerIterator.next(), + onCancel, + ])) + } finally { + waiting = false + } if (done) { return value } yield value } - }()), () => { - cancel({ - done: true, - value: undefined, - }) + }()), async () => { + if (innerIterator) { + innerIterator.return() + } + try { + await onFinally() + } finally { + await cancel() + } }) return Object.assign(cancelableIterator, { - [isCancelableIterator]: true, onCancel, cancel, }) } -CancelableIterator.is = (it) => it[isCancelableIterator] export function pipeline(...iterables) { - const iterators = new Set() + let final function done(err) { - const its = new Set(iterators) - iterators.clear() - its.forEach((it) => { - it.cancel(err) - }) + final.cancel(err) } - return iterables.reduce((prev, next) => { - const it = CancelableIterator(async function* Gen() { + let error + + const last = iterables.reduce((prev, next) => { + return CancelableIterator((async function* Gen() { try { const nextIterable = typeof next === 'function' ? next(prev) : next yield* nextIterable } catch (err) { - done(err) + if (!error) { + error = err + return final.cancel(err) + } + throw err } - }()) - iterators.add(it) - return it + }())) }, undefined) + final = CancelableIterator((async function* Gen() { + yield* last + }()), () => done(error)) + return final } /** @@ -134,8 +175,6 @@ export function pipeline(...iterables) { * Adds abort + end methods to iterator */ -const isStreamIterator = Symbol('StreamIterator') - export function StreamIterator(stream, { abortController, onFinally = () => {}, } = {}) { const onFinallyOnce = pMemoize(onFinally) // called once when stream ends @@ -150,7 +189,6 @@ export function StreamIterator(stream, { abortController, onFinally = () => {}, }) return Object.assign(it, { - [isStreamIterator]: true, stream, async abort() { if (abortController) { diff --git a/test/unit/iterators.test.js b/test/unit/iterators.test.js index 302962368..906de79c5 100644 --- a/test/unit/iterators.test.js +++ b/test/unit/iterators.test.js @@ -1,28 +1,30 @@ -import { Readable } from 'stream' +import { Readable, PassThrough } from 'stream' -import { endStream, iteratorFinally, StreamIterator, CancelableIterator, pipeline, AbortError } from '../../src/iterators' +import { wait } from 'streamr-test-utils' -const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) +import { iteratorFinally, CancelableIterator, pipeline } from '../../src/iterators' +import { Defer } from '../../src/utils' -const expected = [1, 2, 3] +const expected = [1, 2, 3, 4, 5, 6, 7] + +const WAIT = 20 async function* generate(items = expected) { - await wait(5) + await wait(WAIT * 0.1) for await (const item of items) { - await wait(5) + await wait(WAIT * 0.1) yield item - await wait(5) + await wait(WAIT * 0.1) } - await wait(5) + await wait(WAIT * 0.1) } -const WAIT = 50 +const MAX_ITEMS = 2 describe('Iterator Utils', () => { describe('iteratorFinally', () => { let onFinally let onFinallyAfter - const MAX_ITEMS = 2 beforeEach(() => { onFinallyAfter = jest.fn() @@ -50,12 +52,33 @@ describe('Iterator Utils', () => { for await (const msg of iteratorFinally(generate(), onFinally)) { received.push(msg) if (received.length === MAX_ITEMS) { - return + break } } expect(received).toEqual(expected.slice(0, MAX_ITEMS)) }) + it('runs fn when iterator.return() is called asynchronously', async () => { + const received = [] + const itr = iteratorFinally(generate(), onFinally) + let receievedAtCallTime + for await (const msg of itr) { + received.push(msg) + if (received.length === MAX_ITEMS) { + // eslint-disable-next-line no-loop-func + setTimeout(() => { + receievedAtCallTime = received + itr.return() + }) + } + + setTimeout(() => { + itr.return() + }) + } + expect(received).toEqual(receievedAtCallTime) + }) + it('runs fn when iterator throws during iteration', async () => { const received = [] const err = new Error('expected err') @@ -73,12 +96,12 @@ describe('Iterator Utils', () => { it('runs fn when iterator returns + throws during iteration', async () => { const received = [] const err = new Error('expected err') - const it = iteratorFinally(generate(), onFinally) + const itr = iteratorFinally(generate(), onFinally) await expect(async () => { - for await (const msg of it) { + for await (const msg of itr) { received.push(msg) if (received.length === MAX_ITEMS) { - it.return() + itr.return() throw err } } @@ -88,11 +111,11 @@ describe('Iterator Utils', () => { it('runs fn when iterator returns before iteration', async () => { const received = [] - const it = iteratorFinally(generate(), onFinally) - await it.return() + const itr = iteratorFinally(generate(), onFinally) + await itr.return() expect(onFinally).toHaveBeenCalledTimes(1) expect(onFinallyAfter).toHaveBeenCalledTimes(1) - for await (const msg of it) { + for await (const msg of itr) { received.push(msg) } expect(received).toEqual([]) @@ -101,12 +124,12 @@ describe('Iterator Utils', () => { it('runs fn when iterator throws before iteration', async () => { const received = [] const err = new Error('expected err') - const it = iteratorFinally(generate(), onFinally) - await expect(async () => it.throw(err)).rejects.toThrow(err) + const itr = iteratorFinally(generate(), onFinally) + await expect(async () => itr.throw(err)).rejects.toThrow(err) expect(onFinally).toHaveBeenCalledTimes(1) expect(onFinallyAfter).toHaveBeenCalledTimes(1) // doesn't throw, matches native iterators - for await (const msg of it) { + for await (const msg of itr) { received.push(msg) } expect(received).toEqual([]) @@ -114,19 +137,404 @@ describe('Iterator Utils', () => { it('runs fn once', async () => { const received = [] - const it = iteratorFinally(generate(), onFinally) + const itr = iteratorFinally(generate(), onFinally) - for await (const msg of it) { + for await (const msg of itr) { received.push(msg) if (received.length === MAX_ITEMS) { await Promise.all([ - it.return(), - it.return(), + itr.return(), + itr.return(), ]) - return + break } } - expect(received).toEqual([]) + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + }) + }) + + describe('CancelableIterator', () => { + it('runs fn when iterator complete', async () => { + const received = [] + for await (const msg of CancelableIterator(generate())) { + received.push(msg) + } + expect(received).toEqual(expected) + }) + + it('can cancel during iteration', async () => { + const itr = CancelableIterator(generate()) + const received = [] + for await (const msg of itr) { + received.push(msg) + if (received.length === MAX_ITEMS) { + itr.cancel() + } + } + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + }) + + it('cancels when iterator.cancel() is called asynchronously', async () => { + const received = [] + const itr = CancelableIterator(generate()) + let receievedAtCallTime + for await (const msg of itr) { + received.push(msg) + if (received.length === MAX_ITEMS) { + // eslint-disable-next-line no-loop-func + setTimeout(() => { + receievedAtCallTime = received + itr.cancel() + }) + } + } + expect(received).toEqual(receievedAtCallTime) + }) + + it('interrupts outstanding .next call', async () => { + const received = [] + const itr = CancelableIterator(async function* Gen() { + yield* expected + yield await new Promise(() => {}) // would wait forever + }()) + + for await (const msg of itr) { + received.push(msg) + if (received.length === expected.length) { + // eslint-disable-next-line no-loop-func + setTimeout(() => { + itr.cancel() + }) + } + } + expect(received).toEqual(expected) + }) + + it('interrupts outstanding .next call with error', async () => { + const received = [] + const itr = CancelableIterator(async function* Gen() { + yield* expected + yield await new Promise(() => {}) // would wait forever + }()) + + const err = new Error('expected') + + let receievedAtCallTime + await expect(async () => { + for await (const msg of itr) { + received.push(msg) + if (received.length === MAX_ITEMS) { + // eslint-disable-next-line no-loop-func + setTimeout(() => { + receievedAtCallTime = received + itr.cancel(err) + }) + } + } + }).rejects.toThrow(err) + expect(received).toEqual(receievedAtCallTime) + }) + + it('ignores err if cancelled', async () => { + const received = [] + const err = new Error('should not see this') + const d = Defer() + const itr = CancelableIterator(async function* Gen() { + yield* expected + await wait(WAIT * 2) + d.resolve() + throw err + }()) + + let receievedAtCallTime + await expect(async () => { + for await (const msg of itr) { + received.push(msg) + if (received.length === MAX_ITEMS) { + // eslint-disable-next-line no-loop-func + setTimeout(() => { + receievedAtCallTime = received + itr.cancel(err) + }) + } + } + }).rejects.toThrow(err) + await d + await wait(WAIT * 2) + expect(received).toEqual(receievedAtCallTime) + }) + }) + + describe('pipeline', () => { + it('feeds items from one to next', async () => { + const receivedStep1 = [] + const receivedStep2 = [] + const afterStep1 = jest.fn() + const afterStep2 = jest.fn() + const p = pipeline( + generate(), + async function* Step1(s) { + try { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg * 2 + } + } finally { + afterStep1() + } + }, + async function* Step2(s) { + try { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg * 10 + } + } finally { + afterStep2() + } + } + ) + + const received = [] + for await (const msg of p) { + received.push(msg) + if (received.length === expected.length) { + break + } + } + await wait(100) + expect(received).toEqual(expected.map((v) => v * 20)) + expect(receivedStep2).toEqual(expected.map((v) => v * 2)) + expect(receivedStep1).toEqual(expected) + expect(afterStep1).toHaveBeenCalledTimes(1) + expect(afterStep2).toHaveBeenCalledTimes(1) + }) + + it('feeds items from one to next, stops all when start ends', async () => { + const receivedStep1 = [] + const receivedStep2 = [] + const afterStep1 = jest.fn() + const afterStep2 = jest.fn() + const p = pipeline( + expected, + async function* Step1(s) { + try { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg * 2 + } + } finally { + afterStep1() + } + }, + async function* Step2(s) { + try { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg * 10 + } + } finally { + afterStep2() + } + } + ) + + const received = [] + for await (const msg of p) { + received.push(msg) + } + await wait(100) + expect(received).toEqual(expected.map((v) => v * 20)) + expect(receivedStep2).toEqual(expected.map((v) => v * 2)) + expect(receivedStep1).toEqual(expected) + expect(afterStep1).toHaveBeenCalledTimes(1) + expect(afterStep2).toHaveBeenCalledTimes(1) + }) + + it('feeds items from one to next, stops all when middle ends', async () => { + const receivedStep1 = [] + const receivedStep2 = [] + const afterStep1 = jest.fn() + const afterStep2 = jest.fn() + const p = pipeline( + generate(), + async function* Step1(s) { + try { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg * 2 + if (receivedStep1.length === MAX_ITEMS) { + break + } + } + } finally { + afterStep1() + } + }, + async function* Step2(s) { + try { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg * 10 + } + } finally { + afterStep2() + } + } + ) + + const received = [] + for await (const msg of p) { + received.push(msg) + } + await wait(100) + expect(received).toEqual(expected.slice(0, MAX_ITEMS).map((v) => v * 20)) + expect(receivedStep2).toEqual(expected.slice(0, MAX_ITEMS).map((v) => v * 2)) + expect(receivedStep1).toEqual(expected.slice(0, MAX_ITEMS)) + expect(afterStep1).toHaveBeenCalledTimes(1) + expect(afterStep2).toHaveBeenCalledTimes(1) + }) + + it('feeds items from one to next, stops all when middle throws', async () => { + const receivedStep1 = [] + const receivedStep2 = [] + const afterStep1 = jest.fn() + const afterStep2 = jest.fn() + const err = new Error('expected') + const p = pipeline( + generate(), + async function* Step1(s) { + try { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg * 2 + if (receivedStep1.length === MAX_ITEMS) { + throw err + } + } + } finally { + afterStep1() + } + }, + async function* Step2(s) { + try { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg * 10 + } + } finally { + afterStep2() + } + } + ) + + const received = [] + await expect(async () => { + for await (const msg of p) { + received.push(msg) + } + }).rejects.toThrow(err) + await wait(100) + expect(received).toEqual(expected.slice(0, MAX_ITEMS).map((v) => v * 20)) + expect(receivedStep2).toEqual(expected.slice(0, MAX_ITEMS).map((v) => v * 2)) + expect(receivedStep1).toEqual(expected.slice(0, MAX_ITEMS)) + expect(afterStep1).toHaveBeenCalledTimes(1) + expect(afterStep2).toHaveBeenCalledTimes(1) + }) + }) + + describe('stream utilities', () => { + let stream + let onClose + let onError + + beforeEach(() => { + stream = new PassThrough({ + objectMode: true, + }) + onClose = jest.fn() + onError = jest.fn() + stream.once('close', onClose) + stream.once('error', onError) + }) + + describe('StreamIterator', () => { + beforeEach(() => { + Readable.from(generate()).pipe(stream) + }) + + it('closes stream when iterator complete', async () => { + expected.forEach((item) => { + stream.write(item) + }) + const received = [] + for await (const msg of stream) { + received.push(msg) + if (received.length === expected.length) { + break + } + } + expect(onClose).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledTimes(0) + expect(received).toEqual(expected) + }) + + it('closes stream when iterator returns during iteration', async () => { + expected.forEach((item) => { + stream.write(item) + }) + const received = [] + for await (const msg of stream) { + received.push(msg) + if (received.length === MAX_ITEMS) { + break + } + } + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + expect(onClose).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledTimes(0) + }) + + it('closes stream when iterator throws during iteration', async () => { + expected.forEach((item) => { + stream.write(item) + }) + const received = [] + const err = new Error('expected err') + await expect(async () => { + for await (const msg of stream) { + received.push(msg) + if (received.length === MAX_ITEMS) { + throw err + } + } + }).rejects.toThrow(err) + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + expect(onClose).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledTimes(0) + }) + + it('closes stream when iterator returns asynchronously', async () => { + expected.forEach((item) => { + stream.write(item) + }) + const received = [] + const itr = stream[Symbol.asyncIterator]() + let receievedAtCallTime + for await (const msg of itr) { + received.push(msg) + if (received.length === MAX_ITEMS) { + // eslint-disable-next-line no-loop-func + setTimeout(() => { + receievedAtCallTime = received + itr.return() + }) + } + } + expect(received).toEqual(receievedAtCallTime) + expect(onClose).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledTimes(0) + }) }) }) }) From 257a4dcb34f3e0ec7a4a5986f86a1378b4c1e60a Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 12 Oct 2020 09:26:12 -0400 Subject: [PATCH 088/517] Pipeline iterator. --- src/iterators.js | 289 ++++++++++++----- test/unit/iterators.test.js | 629 ++++++++++++++++++++++++++++++++---- 2 files changed, 771 insertions(+), 147 deletions(-) diff --git a/src/iterators.js b/src/iterators.js index 1bc8c541c..342485110 100644 --- a/src/iterators.js +++ b/src/iterators.js @@ -3,6 +3,8 @@ import { promisify } from 'util' import pMemoize from 'p-memoize' +import { Defer } from './utils' + export class AbortError extends Error { constructor(msg = '', ...args) { super(`The operation was aborted. ${msg}`, ...args) @@ -26,147 +28,266 @@ export async function endStream(stream, optionalErr) { } } +/** + * Convert allSettled results into a thrown Aggregate error if necessary. + */ + +class AggregatedError extends Error { + // specifically not using AggregateError name + constructor(errors = [], errorMessage = '') { + super(errorMessage) + this.errors = errors + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor) + } + } +} + +async function allSettledValues(items, errorMessage = '') { + const result = await Promise.allSettled(items) + + const errs = result.filter(({ status }) => status === 'rejected').map(({ reason }) => reason) + if (errs.length) { + throw new AggregatedError(errs, errorMessage) + } + + return result.map(({ value }) => value) +} + /** * Allows injecting a function to execute after an iterator finishes. * Executes finally function even if generator not started. + * Returns new generator. */ -export function iteratorFinally(iterator, onFinally = () => {}) { - let started = false +export function iteratorFinally(iterable, onFinally) { + if (!onFinally) { + // noop if no onFinally + return iterable + } + + // ensure finally only runs once const onFinallyOnce = pMemoize(onFinally) - const g = (async function* It() { + + let started = false + let ended = false + + // wraps return/throw to call onFinally even if generator was never started + const handleFinally = (originalFn) => async (...args) => { + // Important to: + // * only await onFinally if not started + // * call original return/throw *immediately* in either case + // Otherwise: + // * if started, iterator won't stop until onFinally finishes + // * if not started, iterator can still be started before onFinally finishes + // This function handles both cases, but note here as a reminder. + ended = true + if (started) { + return originalFn(...args) + } + + // otherwise iteration can still start if finally function still pending + try { + return await originalFn(...args) + } finally { + await onFinallyOnce() + } + } + + // wrap in generator to track if generator was started + const g = (async function* TrackStarted() { started = true try { - yield* iterator + yield* iterable } finally { await onFinallyOnce() } }()) - // overrides return/throw to call onFinally even if generator was never started - const oldReturn = g.return - const oldThrow = g.throw + const it = g[Symbol.asyncIterator].bind(g) + // replace generator methods return Object.assign(g, { - return: async (...args) => { - if (!started) { - await onFinallyOnce(iterator) + return: handleFinally(g.return.bind(g)), + throw: handleFinally(g.throw.bind(g)), + [Symbol.asyncIterator]() { + if (ended && !started) { + // return a generator that simply runs finally script (once) + return (async function* generatorRunFinally() { // eslint-disable-line require-yield + await onFinallyOnce() + }()) } - return oldReturn.call(g, ...args) - }, - throw: async (...args) => { - if (!started) { - await onFinallyOnce() - } - return oldThrow.call(g, ...args) - }, + return it() + } }) } -export function CancelableIterator(iterable, onFinally = () => {}) { - let cancel +/** + * Creates a generator that can be cancelled and perform optional final cleanup. + * const [cancal, generator] = CancelableGenerator(iterable, onFinally) + */ + +export function CancelableGenerator(iterable, onFinally) { + let started = false let cancelled = false - let cancelableIterator + let finalCalled = false + let pendingNextCount = 0 let error - let waiting = false - - const onCancel = new Promise((resolve, reject) => { - cancel = (value) => { - if (cancelled) { return } - cancelled = true + const onCancel = Defer() + const onDone = Defer() - if (value instanceof Error) { - error = value - if (!waiting) { - cancelableIterator.throw(error) - return - } - reject(error) - return + const cancel = (gtr) => async (value) => { + if (value instanceof Error) { + if (value !== error) { + // collect errors + error = !error + ? value + : new AggregatedError([value, ...(error.errors || [])], value.message) } + } - if (!waiting) { - cancelableIterator.return() - return + if (cancelled) { + // prevent recursion + return onDone + } + + cancelled = true + + // need to make sure we don't try return inside final otherwise we end up deadlocked + if (!finalCalled && !pendingNextCount) { + // try end generator + const onGenEnd = error + ? gtr.throw(error).catch(() => {}) + : gtr.return() + + // wait for generator if it didn't start + // i.e. wait for finally + if (!started) { + await onGenEnd + return onDone } + } + if (error) { + onCancel.reject(error) + } else { + onCancel.resolve({ + value, + done: true, + }) + } + + return onDone + } + + async function* CancelableGeneratorFn() { + started = true + + // manually iterate + const iterator = iterable[Symbol.asyncIterator]() + + // keep track of pending calls to next() + // so we can cancel early if nothing pending + async function next(...args) { + // use symbol instead of true so we can tell if called multiple times + // see === comparison below + pendingNextCount += 1 try { - resolve({ - value, - done: true, - }) - } catch (err) { - reject(err) + return await iterator.next(...args) + } finally { + pendingNextCount = Math.max(0, pendingNextCount - 1) // eslint-disable-line require-atomic-updates } } - }).finally(() => { - cancel = () => onCancel - }) - let innerIterator - cancelableIterator = iteratorFinally((async function* Gen() { - innerIterator = iterable[Symbol.asyncIterator]() - while (true) { - waiting = true - let value - let done - try { - // eslint-disable-next-line no-await-in-loop - ({ value, done } = await Promise.race([ - innerIterator.next(), + try { + yield* { + // here is the meat: + // each next() races against cancel promise + next: async (...args) => Promise.race([ + next(...args), onCancel, - ])) - } finally { - waiting = false + ]), + [Symbol.asyncIterator]() { + return this + }, } - if (done) { - return value + } finally { + // try end iterator + if (iterator) { + if (pendingNextCount) { + iterator.return() + } else { + await iterator.return() + } } - yield value - } - }()), async () => { - if (innerIterator) { - innerIterator.return() } + } + + const cancelableGenerator = iteratorFinally(CancelableGeneratorFn(), async () => { + finalCalled = true try { - await onFinally() + if (onFinally) { + await onFinally() + } } finally { - await cancel() + onDone.resolve() + } + + // error whole generator, for await of will reject. + if (error) { + throw error } - }) - return Object.assign(cancelableIterator, { - onCancel, - cancel, + return onDone }) + + return [ + cancel(cancelableGenerator), + cancelableGenerator + ] } +/** + * Pipeline of async generators + */ + export function pipeline(...iterables) { - let final - function done(err) { - final.cancel(err) + const cancelFns = new Set() + async function cancelAll(err) { + try { + await allSettledValues([...cancelFns].map(async (cancel) => ( + cancel(err) + ))) + } finally { + cancelFns.clear() + } } let error - const last = iterables.reduce((prev, next) => { - return CancelableIterator((async function* Gen() { + const [cancelCurrent, it] = CancelableGenerator((async function* Gen() { try { const nextIterable = typeof next === 'function' ? next(prev) : next yield* nextIterable } catch (err) { if (!error) { error = err - return final.cancel(err) + cancelAll(err) } throw err } }())) + cancelFns.add(cancelCurrent) + return it }, undefined) - final = CancelableIterator((async function* Gen() { - yield* last - }()), () => done(error)) - return final + + const pipelineValue = iteratorFinally(last, () => { + cancelFns.clear() + }) + + return Object.assign(pipelineValue, { + cancel: cancelAll, + }) } /** diff --git a/test/unit/iterators.test.js b/test/unit/iterators.test.js index 906de79c5..b96a86483 100644 --- a/test/unit/iterators.test.js +++ b/test/unit/iterators.test.js @@ -2,10 +2,10 @@ import { Readable, PassThrough } from 'stream' import { wait } from 'streamr-test-utils' -import { iteratorFinally, CancelableIterator, pipeline } from '../../src/iterators' +import { iteratorFinally, CancelableGenerator, pipeline } from '../../src/iterators' import { Defer } from '../../src/utils' -const expected = [1, 2, 3, 4, 5, 6, 7] +const expected = [1, 2, 3, 4, 5, 6, 7, 8] const WAIT = 20 @@ -21,7 +21,103 @@ async function* generate(items = expected) { const MAX_ITEMS = 2 +function IteratorTest(name, fn) { + describe(`${name} IteratorTest`, () => { + it('runs to completion', async () => { + const received = [] + const itr = fn() + for await (const msg of itr) { + received.push(msg) + } + expect(received).toEqual(expected) + }) + + it('can return in finally', async () => { + const received = [] + const itr = (async function* Outer() { + const innerItr = fn()[Symbol.asyncIterator]() + try { + yield* innerItr + } finally { + await innerItr.return() // note itr.return would block + } + }()) + + for await (const msg of itr) { + received.push(msg) + if (received.length === MAX_ITEMS) { + break + } + } + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + }) + + it('can return mid-iteration', async () => { + const received = [] + for await (const msg of fn()) { + received.push(msg) + if (received.length === MAX_ITEMS) { + break + } + } + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + }) + + it('can throw mid-iteration', async () => { + const received = [] + const err = new Error('expected err') + await expect(async () => { + for await (const msg of fn()) { + received.push(msg) + if (received.length === MAX_ITEMS) { + throw err + } + } + }).rejects.toThrow(err) + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + }) + + it('can throw before iterating', async () => { + const received = [] + const itr = fn()[Symbol.asyncIterator]() + const err = new Error('expected err') + + await expect(async () => { + await itr.throw(err) + }).rejects.toThrow(err) + + // does not throw + for await (const msg of itr) { + received.push(msg) + } + expect(received).toEqual([]) + }) + + it('can return before iterating', async () => { + const itr = fn()[Symbol.asyncIterator]() + await itr.return() + const received = [] + for await (const msg of itr) { + received.push(msg) + } + expect(received).toEqual([]) + }) + + it('can queue next calls', async () => { + const itr = fn()[Symbol.asyncIterator]() + const tasks = expected.map(async () => itr.next()) + const received = await Promise.all(tasks) + expect(received.map(({ value }) => value)).toEqual(expected) + await itr.return() + }) + }) +} + describe('Iterator Utils', () => { + describe('compare native generators', () => { + IteratorTest('baseline', () => generate()) + }) + describe('iteratorFinally', () => { let onFinally let onFinallyAfter @@ -39,23 +135,8 @@ describe('Iterator Utils', () => { expect(onFinallyAfter).toHaveBeenCalledTimes(1) }) - it('runs fn when iterator complete', async () => { - const received = [] - for await (const msg of iteratorFinally(generate(), onFinally)) { - received.push(msg) - } - expect(received).toEqual(expected) - }) - - it('runs fn when iterator returns during iteration', async () => { - const received = [] - for await (const msg of iteratorFinally(generate(), onFinally)) { - received.push(msg) - if (received.length === MAX_ITEMS) { - break - } - } - expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + describe('iteratorFinally iteratorTests', () => { + IteratorTest('iteratorFinally', () => iteratorFinally(generate(), onFinally)) }) it('runs fn when iterator.return() is called asynchronously', async () => { @@ -79,17 +160,16 @@ describe('Iterator Utils', () => { expect(received).toEqual(receievedAtCallTime) }) - it('runs fn when iterator throws during iteration', async () => { + it('runs fn when iterator returns + breaks during iteration', async () => { const received = [] - const err = new Error('expected err') - await expect(async () => { - for await (const msg of iteratorFinally(generate(), onFinally)) { - received.push(msg) - if (received.length === MAX_ITEMS) { - throw err - } + const itr = iteratorFinally(generate(), onFinally) + for await (const msg of itr) { + received.push(msg) + if (received.length === MAX_ITEMS) { + itr.return() // no await + break } - }).rejects.toThrow(err) + } expect(received).toEqual(expected.slice(0, MAX_ITEMS)) }) @@ -101,7 +181,7 @@ describe('Iterator Utils', () => { for await (const msg of itr) { received.push(msg) if (received.length === MAX_ITEMS) { - itr.return() + itr.return() // no await throw err } } @@ -121,6 +201,43 @@ describe('Iterator Utils', () => { expect(received).toEqual([]) }) + it('runs fn when iterator returns before iteration', async () => { + const received = [] + const onStarted = jest.fn() + const itr = iteratorFinally((async function* Test() { + onStarted() + yield* generate() + }()), async () => { + await wait(WAIT * 5) + await onFinally() + }) + itr.return() // no await + for await (const msg of itr) { + received.push(msg) + } + expect(onStarted).toHaveBeenCalledTimes(0) + expect(received).toEqual([]) + }) + + it('runs finally once, waits for outstanding if returns before iteration', async () => { + const received = [] + const itr = iteratorFinally(generate(), onFinally) + + const t1 = itr.return() + const t2 = itr.return() + await Promise.race([t1, t2]) + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) + await Promise.all([t1, t2]) + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) + for await (const msg of itr) { + received.push(msg) + } + + expect(received).toEqual([]) + }) + it('runs fn when iterator throws before iteration', async () => { const received = [] const err = new Error('expected err') @@ -135,48 +252,131 @@ describe('Iterator Utils', () => { expect(received).toEqual([]) }) - it('runs fn once', async () => { + it('works nested', async () => { + const onFinallyAfterInner = jest.fn() + const onFinallyInner = jest.fn(async () => { + await wait(WAIT) + onFinallyAfterInner() + }) + const itrInner = iteratorFinally(generate(), onFinallyInner) + const itr = iteratorFinally(itrInner, onFinally) + + const received = [] + for await (const msg of itr) { + received.push(msg) + if (received.length === MAX_ITEMS) { + break + } + } + + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyAfterInner).toHaveBeenCalledTimes(1) + }) + + it('runs finally once, waits for outstanding', async () => { const received = [] const itr = iteratorFinally(generate(), onFinally) for await (const msg of itr) { received.push(msg) if (received.length === MAX_ITEMS) { - await Promise.all([ - itr.return(), - itr.return(), - ]) + const t1 = itr.return() + const t2 = itr.return() + await Promise.race([t1, t2]) + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) + await Promise.all([t1, t2]) + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) break } } + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) }) - }) - describe('CancelableIterator', () => { - it('runs fn when iterator complete', async () => { + it.skip('can call return() inside finally function', async () => { const received = [] - for await (const msg of CancelableIterator(generate())) { + const itr = iteratorFinally(generate(), async () => { + await onFinally() + await itr.return() + }) + + for await (const msg of itr) { received.push(msg) + if (received.length === MAX_ITEMS) { + break + } } - expect(received).toEqual(expected) + + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + }) + }) + + describe('CancelableGenerator', () => { + let onFinally + let onFinallyAfter + + beforeEach(() => { + onFinallyAfter = jest.fn() + onFinally = jest.fn(async () => { + await wait(WAIT) + onFinallyAfter() + }) + }) + + afterEach(() => { + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) + }) + + IteratorTest('CancelableGenerator', () => { + const [, itr] = CancelableGenerator(generate(), onFinally) + return itr }) it('can cancel during iteration', async () => { - const itr = CancelableIterator(generate()) + const [cancel, itr] = CancelableGenerator(generate(), onFinally) const received = [] for await (const msg of itr) { received.push(msg) if (received.length === MAX_ITEMS) { - itr.cancel() + cancel() } } + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) }) + it('can cancel before iteration', async () => { + const [cancel, itr] = CancelableGenerator(generate(), onFinally) + const received = [] + cancel() + for await (const msg of itr) { + received.push(msg) + } + + expect(received).toEqual([]) + }) + + it('can cancel with error before iteration', async () => { + const [cancel, itr] = CancelableGenerator(generate(), onFinally) + const received = [] + const err = new Error('expected') + cancel(err) + await expect(async () => { + for await (const msg of itr) { + received.push(msg) + } + }).rejects.toThrow(err) + + expect(received).toEqual([]) + }) + it('cancels when iterator.cancel() is called asynchronously', async () => { const received = [] - const itr = CancelableIterator(generate()) + const [cancel, itr] = CancelableGenerator(generate(), onFinally) let receievedAtCallTime for await (const msg of itr) { received.push(msg) @@ -184,38 +384,84 @@ describe('Iterator Utils', () => { // eslint-disable-next-line no-loop-func setTimeout(() => { receievedAtCallTime = received - itr.cancel() + cancel() }) } } + expect(received).toEqual(receievedAtCallTime) }) it('interrupts outstanding .next call', async () => { const received = [] - const itr = CancelableIterator(async function* Gen() { + const [cancel, itr] = CancelableGenerator((async function* Gen() { yield* expected yield await new Promise(() => {}) // would wait forever - }()) + }()), onFinally) + + for await (const msg of itr) { + received.push(msg) + if (received.length === expected.length) { + await cancel() + } + } + + expect(received).toEqual(expected) + }) + + it('interrupts outstanding .next call when called asynchronously', async () => { + const received = [] + const [cancel, itr] = CancelableGenerator((async function* Gen() { + yield* expected + yield await new Promise(() => {}) // would wait forever + }()), onFinally) for await (const msg of itr) { received.push(msg) if (received.length === expected.length) { // eslint-disable-next-line no-loop-func setTimeout(() => { - itr.cancel() + cancel() }) } } + expect(received).toEqual(expected) }) + it('stops iterator', async () => { + const shouldRunFinally = jest.fn() + const [cancel, itr] = CancelableGenerator((async function* Gen() { + try { + yield 1 + await wait(WAIT) + yield 2 + await wait(WAIT) + yield 3 + } finally { + shouldRunFinally() + } + }()), onFinally) + + const received = [] + for await (const msg of itr) { + received.push(msg) + if (received.length === 2) { + cancel() + } + } + + expect(received).toEqual([1, 2]) + await wait(WAIT) + expect(shouldRunFinally).toHaveBeenCalledTimes(1) + }) + it('interrupts outstanding .next call with error', async () => { const received = [] - const itr = CancelableIterator(async function* Gen() { + const [cancel, itr] = CancelableGenerator((async function* Gen() { yield* expected yield await new Promise(() => {}) // would wait forever - }()) + }()), onFinally) const err = new Error('expected') @@ -227,24 +473,70 @@ describe('Iterator Utils', () => { // eslint-disable-next-line no-loop-func setTimeout(() => { receievedAtCallTime = received - itr.cancel(err) + cancel(err) }) } } }).rejects.toThrow(err) + expect(received).toEqual(receievedAtCallTime) }) + it('can handle queued next calls', async () => { + const triggeredForever = jest.fn() + const [cancel, itr] = CancelableGenerator((async function* Gen() { + yield* expected + setTimeout(() => { + cancel() + }, WAIT * 2) + yield await new Promise(() => { + triggeredForever() + }) // would wait forever + }()), onFinally) + + const tasks = expected.map(async () => itr.next()) + tasks.push(itr.next()) // one more over the edge (should trigger forever promise) + const received = await Promise.all(tasks) + expect(received.map(({ value }) => value)).toEqual([...expected, undefined]) + expect(triggeredForever).toHaveBeenCalledTimes(1) + }) + + it('can handle queued next calls resolving out of order', async () => { + const triggeredForever = jest.fn() + const [cancel, itr] = CancelableGenerator((async function* Gen() { + let i = 0 + for await (const v of expected) { + i += 1 + await wait((expected.length - i - 1) * 2 * WAIT) + yield v + } + + setTimeout(() => { + cancel() + }, WAIT * 2) + + yield await new Promise(() => { + triggeredForever() + }) // would wait forever + }()), onFinally) + + const tasks = expected.map(async () => itr.next()) + tasks.push(itr.next()) // one more over the edge (should trigger forever promise) + const received = await Promise.all(tasks) + expect(received.map(({ value }) => value)).toEqual([...expected, undefined]) + expect(triggeredForever).toHaveBeenCalledTimes(1) + }) + it('ignores err if cancelled', async () => { const received = [] - const err = new Error('should not see this') + const err = new Error('expected') const d = Defer() - const itr = CancelableIterator(async function* Gen() { + const [cancel, itr] = CancelableGenerator((async function* Gen() { yield* expected await wait(WAIT * 2) d.resolve() - throw err - }()) + throw new Error('should not see this') + }()), onFinally) let receievedAtCallTime await expect(async () => { @@ -254,23 +546,144 @@ describe('Iterator Utils', () => { // eslint-disable-next-line no-loop-func setTimeout(() => { receievedAtCallTime = received - itr.cancel(err) + cancel(err) }) } } }).rejects.toThrow(err) + await d await wait(WAIT * 2) + expect(received).toEqual(receievedAtCallTime) }) + + it('can cancel nested cancellable iterator in finally', async () => { + const onFinallyInner = jest.fn() + const waitInner = jest.fn() + const [cancelInner, itrInner] = CancelableGenerator((async function* Gen() { + yield* generate() + yield await new Promise(() => { + // should not get here + waitInner() + }) // would wait forever + }()), onFinallyInner) + + const waitOuter = jest.fn() + const [cancelOuter, itrOuter] = CancelableGenerator((async function* Gen() { + yield* itrInner + yield await new Promise(() => { + // should not get here + waitOuter() + }) // would wait forever + }()), async () => { + await cancelInner() + await onFinally() + }) + + const received = [] + for await (const msg of itrOuter) { + received.push(msg) + if (received.length === expected.length) { + await cancelOuter() + } + } + + expect(waitOuter).toHaveBeenCalledTimes(0) + expect(waitInner).toHaveBeenCalledTimes(0) + expect(received).toEqual(expected) + expect(onFinallyInner).toHaveBeenCalledTimes(1) + }) + + it('can cancels nested cancellable iterator in finally, asynchronously', async () => { + const onFinallyInner = jest.fn() + const waitInner = jest.fn() + const [cancelInner, itrInner] = CancelableGenerator((async function* Gen() { + yield* generate() + yield await new Promise(() => { + // should not get here + waitInner() + }) // would wait forever + }()), onFinallyInner) + + const waitOuter = jest.fn() + const [cancelOuter, itrOuter] = CancelableGenerator((async function* Gen() { + yield* itrInner + yield await new Promise(() => { + // should not get here + waitOuter() + }) // would wait forever + }()), async () => { + await cancelInner() + await onFinally() + }) + + const received = [] + for await (const msg of itrOuter) { + received.push(msg) + if (received.length === expected.length) { + setTimeout(() => { + cancelOuter() + }) + } + } + + expect(waitOuter).toHaveBeenCalledTimes(1) + expect(waitInner).toHaveBeenCalledTimes(1) + expect(received).toEqual(expected) + expect(onFinallyInner).toHaveBeenCalledTimes(1) + }) + + it('can cancel in parallel and wait correctly for both', async () => { + const [cancel, itr] = CancelableGenerator(generate(), onFinally) + const ranTests = jest.fn() + + const received = [] + for await (const msg of itr) { + received.push(msg) + if (received.length === MAX_ITEMS) { + const t1 = cancel() + const t2 = cancel() + await Promise.race([t1, t2]) + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) + await Promise.all([t1, t2]) + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) + ranTests() + } + } + + expect(ranTests).toHaveBeenCalledTimes(1) + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + }) }) describe('pipeline', () => { + describe('baseline', () => { + IteratorTest('pipeline', () => { + return pipeline( + generate(), + async function* Step1(s) { + for await (const msg of s) { + yield msg * 2 + } + }, + async function* Step2(s) { + for await (const msg of s) { + yield msg / 2 + } + } + ) + }) + }) + it('feeds items from one to next', async () => { const receivedStep1 = [] const receivedStep2 = [] const afterStep1 = jest.fn() const afterStep2 = jest.fn() + const p = pipeline( generate(), async function* Step1(s) { @@ -290,6 +703,8 @@ describe('Iterator Utils', () => { yield msg * 10 } } finally { + // ensure async finally works + await wait(WAIT) afterStep2() } } @@ -298,11 +713,8 @@ describe('Iterator Utils', () => { const received = [] for await (const msg of p) { received.push(msg) - if (received.length === expected.length) { - break - } } - await wait(100) + expect(received).toEqual(expected.map((v) => v * 20)) expect(receivedStep2).toEqual(expected.map((v) => v * 2)) expect(receivedStep1).toEqual(expected) @@ -343,7 +755,7 @@ describe('Iterator Utils', () => { for await (const msg of p) { received.push(msg) } - await wait(100) + expect(received).toEqual(expected.map((v) => v * 20)) expect(receivedStep2).toEqual(expected.map((v) => v * 2)) expect(receivedStep1).toEqual(expected) @@ -356,6 +768,7 @@ describe('Iterator Utils', () => { const receivedStep2 = [] const afterStep1 = jest.fn() const afterStep2 = jest.fn() + const p = pipeline( generate(), async function* Step1(s) { @@ -387,7 +800,7 @@ describe('Iterator Utils', () => { for await (const msg of p) { received.push(msg) } - await wait(100) + expect(received).toEqual(expected.slice(0, MAX_ITEMS).map((v) => v * 20)) expect(receivedStep2).toEqual(expected.slice(0, MAX_ITEMS).map((v) => v * 2)) expect(receivedStep1).toEqual(expected.slice(0, MAX_ITEMS)) @@ -401,6 +814,7 @@ describe('Iterator Utils', () => { const afterStep1 = jest.fn() const afterStep2 = jest.fn() const err = new Error('expected') + const p = pipeline( generate(), async function* Step1(s) { @@ -434,13 +848,95 @@ describe('Iterator Utils', () => { received.push(msg) } }).rejects.toThrow(err) - await wait(100) + expect(received).toEqual(expected.slice(0, MAX_ITEMS).map((v) => v * 20)) expect(receivedStep2).toEqual(expected.slice(0, MAX_ITEMS).map((v) => v * 2)) expect(receivedStep1).toEqual(expected.slice(0, MAX_ITEMS)) expect(afterStep1).toHaveBeenCalledTimes(1) expect(afterStep2).toHaveBeenCalledTimes(1) }) + + it('handles errors before', async () => { + const err = new Error('expected') + + const p = pipeline( + generate(), + async function* Step1(s) { + yield* s + throw err + }, + async function* Step2(s) { + yield* s + yield await new Promise(() => {}) // would wait forever + } + ) + + const received = [] + await expect(async () => { + for await (const msg of p) { + received.push(msg) + } + }).rejects.toThrow(err) + + expect(received).toEqual(expected) + }) + + it('handles errors after', async () => { + const err = new Error('expected') + + const p = pipeline( + generate(), + async function* Step1(s) { + yield* s + }, + async function* Step2(s) { + yield* s + throw err + } + ) + + const received = [] + await expect(async () => { + for await (const msg of p) { + received.push(msg) + } + }).rejects.toThrow(err) + + expect(received).toEqual(expected) + }) + + it('handles errors after', async () => { + const err = new Error('expected') + const receivedStep2 = [] + const shouldNotGetHere = jest.fn() + + const p = pipeline( + generate(), + async function* Step1(s) { + yield* s + }, + async function* Step2(s) { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg + if (receivedStep2.length === MAX_ITEMS) { + await p.cancel(err) + shouldNotGetHere() + } + } + } + ) + + const received = [] + await expect(async () => { + for await (const msg of p) { + received.push(msg) + } + }).rejects.toThrow(err) + + expect(shouldNotGetHere).toHaveBeenCalledTimes(0) + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + }) }) describe('stream utilities', () => { @@ -467,6 +963,7 @@ describe('Iterator Utils', () => { expected.forEach((item) => { stream.write(item) }) + const received = [] for await (const msg of stream) { received.push(msg) @@ -474,6 +971,7 @@ describe('Iterator Utils', () => { break } } + expect(onClose).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(0) expect(received).toEqual(expected) @@ -483,6 +981,7 @@ describe('Iterator Utils', () => { expected.forEach((item) => { stream.write(item) }) + const received = [] for await (const msg of stream) { received.push(msg) @@ -490,6 +989,7 @@ describe('Iterator Utils', () => { break } } + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) expect(onClose).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(0) @@ -499,6 +999,7 @@ describe('Iterator Utils', () => { expected.forEach((item) => { stream.write(item) }) + const received = [] const err = new Error('expected err') await expect(async () => { @@ -509,6 +1010,7 @@ describe('Iterator Utils', () => { } } }).rejects.toThrow(err) + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) expect(onClose).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(0) @@ -518,9 +1020,10 @@ describe('Iterator Utils', () => { expected.forEach((item) => { stream.write(item) }) - const received = [] + const itr = stream[Symbol.asyncIterator]() let receievedAtCallTime + const received = [] for await (const msg of itr) { received.push(msg) if (received.length === MAX_ITEMS) { @@ -531,6 +1034,7 @@ describe('Iterator Utils', () => { }) } } + expect(received).toEqual(receievedAtCallTime) expect(onClose).toHaveBeenCalledTimes(1) expect(onError).toHaveBeenCalledTimes(0) @@ -538,4 +1042,3 @@ describe('Iterator Utils', () => { }) }) }) - From e8c8e5d797bbaf970d0e210099e6ded15688a712 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 12 Oct 2020 09:55:12 -0400 Subject: [PATCH 089/517] Clean up stream tests. --- test/unit/iterators.test.js | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/test/unit/iterators.test.js b/test/unit/iterators.test.js index b96a86483..ffe4b1f75 100644 --- a/test/unit/iterators.test.js +++ b/test/unit/iterators.test.js @@ -960,10 +960,6 @@ describe('Iterator Utils', () => { }) it('closes stream when iterator complete', async () => { - expected.forEach((item) => { - stream.write(item) - }) - const received = [] for await (const msg of stream) { received.push(msg) @@ -978,10 +974,6 @@ describe('Iterator Utils', () => { }) it('closes stream when iterator returns during iteration', async () => { - expected.forEach((item) => { - stream.write(item) - }) - const received = [] for await (const msg of stream) { received.push(msg) @@ -995,11 +987,21 @@ describe('Iterator Utils', () => { expect(onError).toHaveBeenCalledTimes(0) }) - it('closes stream when iterator throws during iteration', async () => { - expected.forEach((item) => { - stream.write(item) - }) + it('closes stream when stream ends during iteration', async () => { + const received = [] + for await (const msg of stream) { + received.push(msg) + if (received.length === MAX_ITEMS) { + stream.end() + } + } + + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + expect(onClose).toHaveBeenCalledTimes(1) + expect(onError).toHaveBeenCalledTimes(0) + }) + it('closes stream when iterator throws during iteration', async () => { const received = [] const err = new Error('expected err') await expect(async () => { @@ -1017,10 +1019,6 @@ describe('Iterator Utils', () => { }) it('closes stream when iterator returns asynchronously', async () => { - expected.forEach((item) => { - stream.write(item) - }) - const itr = stream[Symbol.asyncIterator]() let receievedAtCallTime const received = [] From 4ad4bd6d79027f1d0ee1314654dd3d78959a586d Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 12 Oct 2020 13:01:13 -0400 Subject: [PATCH 090/517] Test cancel/finally nesting better. --- src/iterators.js | 37 ++++- test/unit/iterators.test.js | 286 ++++++++++++++++++++++++------------ 2 files changed, 221 insertions(+), 102 deletions(-) diff --git a/src/iterators.js b/src/iterators.js index 342485110..739c0d136 100644 --- a/src/iterators.js +++ b/src/iterators.js @@ -67,10 +67,13 @@ export function iteratorFinally(iterable, onFinally) { } // ensure finally only runs once - const onFinallyOnce = pMemoize(onFinally) + const onFinallyOnce = pMemoize(onFinally, { + cacheKey: () => true // always same key + }) let started = false let ended = false + let error // wraps return/throw to call onFinally even if generator was never started const handleFinally = (originalFn) => async (...args) => { @@ -89,8 +92,13 @@ export function iteratorFinally(iterable, onFinally) { // otherwise iteration can still start if finally function still pending try { return await originalFn(...args) + } catch (err) { + if (!error) { + error = err + } + throw err } finally { - await onFinallyOnce() + await onFinallyOnce(error) } } @@ -99,8 +107,13 @@ export function iteratorFinally(iterable, onFinally) { started = true try { yield* iterable + } catch (err) { + if (!error) { + error = err + } + throw err } finally { - await onFinallyOnce() + await onFinallyOnce(error) } }()) @@ -113,6 +126,10 @@ export function iteratorFinally(iterable, onFinally) { if (ended && !started) { // return a generator that simply runs finally script (once) return (async function* generatorRunFinally() { // eslint-disable-line require-yield + if (typeof iterable.return === 'function') { + await iterable.return() // run onFinally for nested iterable + } + await onFinallyOnce() }()) } @@ -180,11 +197,12 @@ export function CancelableGenerator(iterable, onFinally) { return onDone } + let iterator async function* CancelableGeneratorFn() { started = true // manually iterate - const iterator = iterable[Symbol.asyncIterator]() + iterator = iterable[Symbol.asyncIterator]() // keep track of pending calls to next() // so we can cancel early if nothing pending @@ -226,6 +244,13 @@ export function CancelableGenerator(iterable, onFinally) { const cancelableGenerator = iteratorFinally(CancelableGeneratorFn(), async () => { finalCalled = true try { + // cancel inner if has cancel + if (iterable && iterable.cancel) { + await iterable.cancel() + } else if (iterator && iterator.cancel) { + await iterator.cancel() + } + if (onFinally) { await onFinally() } @@ -241,8 +266,10 @@ export function CancelableGenerator(iterable, onFinally) { return onDone }) + const cancelFn = cancel(cancelableGenerator) + cancelableGenerator.cancel = cancelFn return [ - cancel(cancelableGenerator), + cancelFn, cancelableGenerator ] } diff --git a/test/unit/iterators.test.js b/test/unit/iterators.test.js index ffe4b1f75..85b692bec 100644 --- a/test/unit/iterators.test.js +++ b/test/unit/iterators.test.js @@ -1,10 +1,13 @@ import { Readable, PassThrough } from 'stream' +import Debug from 'debug' import { wait } from 'streamr-test-utils' import { iteratorFinally, CancelableGenerator, pipeline } from '../../src/iterators' import { Defer } from '../../src/utils' +console.log = Debug('Streamr:: CONSOLE ') + const expected = [1, 2, 3, 4, 5, 6, 7, 8] const WAIT = 20 @@ -173,6 +176,24 @@ describe('Iterator Utils', () => { expect(received).toEqual(expected.slice(0, MAX_ITEMS)) }) + it('does not call inner iterators onFinally with error if outer errors', async () => { + // maybe not desirable, but captures existing behaviour. + // this matches native generator/iterator behaviour, only outermost iteration actually errors + const received = [] + const err = new Error('expected err') + const itr = iteratorFinally(generate(), onFinally) + await expect(async () => { + for await (const msg of itr) { + received.push(msg) + if (received.length === MAX_ITEMS) { + throw err + } + } + }).rejects.toThrow(err) + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + expect(onFinally).not.toHaveBeenCalledWith(err) + }) + it('runs fn when iterator returns + throws during iteration', async () => { const received = [] const err = new Error('expected err') @@ -187,6 +208,7 @@ describe('Iterator Utils', () => { } }).rejects.toThrow(err) expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + expect(onFinally).not.toHaveBeenCalledWith(err) // only outer onFinally will have err }) it('runs fn when iterator returns before iteration', async () => { @@ -252,26 +274,92 @@ describe('Iterator Utils', () => { expect(received).toEqual([]) }) - it('works nested', async () => { - const onFinallyAfterInner = jest.fn() - const onFinallyInner = jest.fn(async () => { - await wait(WAIT) - onFinallyAfterInner() + describe('nesting', () => { + let onFinallyAfterInner + let onFinallyInner + + beforeEach(() => { + onFinallyAfterInner = jest.fn() + const afterInner = onFinallyAfterInner // capture so won't run afterInner of another test + onFinallyInner = jest.fn(async () => { + await wait(WAIT) + afterInner() + }) }) - const itrInner = iteratorFinally(generate(), onFinallyInner) - const itr = iteratorFinally(itrInner, onFinally) - const received = [] - for await (const msg of itr) { - received.push(msg) - if (received.length === MAX_ITEMS) { - break + afterEach(() => { + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyAfterInner).toHaveBeenCalledTimes(1) + }) + + IteratorTest('iteratorFinally nested', () => { + const itrInner = iteratorFinally(generate(), onFinallyInner) + return iteratorFinally(itrInner, onFinally) + }) + + it('works nested', async () => { + const itrInner = iteratorFinally(generate(), onFinallyInner) + const itr = iteratorFinally(itrInner, onFinally) + + const received = [] + for await (const msg of itr) { + received.push(msg) + if (received.length === MAX_ITEMS) { + break + } } - } - expect(received).toEqual(expected.slice(0, MAX_ITEMS)) - expect(onFinallyInner).toHaveBeenCalledTimes(1) - expect(onFinallyAfterInner).toHaveBeenCalledTimes(1) + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + }) + + it('calls iterator onFinally with error if outer errors', async () => { + const received = [] + const err = new Error('expected err') + const innerItr = iteratorFinally(generate(), onFinallyInner) + const itr = iteratorFinally((async function* Outer() { + for await (const msg of innerItr) { + yield msg + if (received.length === MAX_ITEMS) { + throw err + } + } + }()), onFinally) + + await expect(async () => { + for await (const msg of itr) { + received.push(msg) + } + }).rejects.toThrow(err) + + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + expect(onFinally).toHaveBeenCalledWith(err) + expect(onFinallyInner).not.toHaveBeenCalledWith(err) + }) + + it('calls iterator onFinally with error if inner errors', async () => { + const received = [] + const err = new Error('expected err') + const itrInner = iteratorFinally((async function* Outer() { + for await (const msg of generate()) { + yield msg + if (received.length === MAX_ITEMS) { + throw err + } + } + }()), onFinallyInner) + const itr = iteratorFinally(itrInner, onFinally) + + await expect(async () => { + for await (const msg of itr) { + received.push(msg) + } + }).rejects.toThrow(err) + + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + // both should see error + expect(onFinally).toHaveBeenCalledWith(err) + expect(onFinallyInner).toHaveBeenCalledWith(err) + }) }) it('runs finally once, waits for outstanding', async () => { @@ -295,23 +383,6 @@ describe('Iterator Utils', () => { expect(received).toEqual(expected.slice(0, MAX_ITEMS)) }) - - it.skip('can call return() inside finally function', async () => { - const received = [] - const itr = iteratorFinally(generate(), async () => { - await onFinally() - await itr.return() - }) - - for await (const msg of itr) { - received.push(msg) - if (received.length === MAX_ITEMS) { - break - } - } - - expect(received).toEqual(expected.slice(0, MAX_ITEMS)) - }) }) describe('CancelableGenerator', () => { @@ -558,80 +629,101 @@ describe('Iterator Utils', () => { expect(received).toEqual(receievedAtCallTime) }) - it('can cancel nested cancellable iterator in finally', async () => { - const onFinallyInner = jest.fn() - const waitInner = jest.fn() - const [cancelInner, itrInner] = CancelableGenerator((async function* Gen() { - yield* generate() - yield await new Promise(() => { - // should not get here - waitInner() - }) // would wait forever - }()), onFinallyInner) + describe('nesting', () => { + let onFinallyAfterInner + let onFinallyInner - const waitOuter = jest.fn() - const [cancelOuter, itrOuter] = CancelableGenerator((async function* Gen() { - yield* itrInner - yield await new Promise(() => { - // should not get here - waitOuter() - }) // would wait forever - }()), async () => { - await cancelInner() - await onFinally() + beforeEach(() => { + onFinallyAfterInner = jest.fn() + const afterInner = onFinallyAfterInner // capture so won't run afterInner of another test + onFinallyInner = jest.fn(async () => { + await wait(WAIT) + afterInner() + }) }) - const received = [] - for await (const msg of itrOuter) { - received.push(msg) - if (received.length === expected.length) { - await cancelOuter() - } - } + afterEach(() => { + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyAfterInner).toHaveBeenCalledTimes(1) + }) - expect(waitOuter).toHaveBeenCalledTimes(0) - expect(waitInner).toHaveBeenCalledTimes(0) - expect(received).toEqual(expected) - expect(onFinallyInner).toHaveBeenCalledTimes(1) - }) + IteratorTest('CancelableGenerator nested', () => { + const [, itrInner] = CancelableGenerator(generate(), onFinallyInner) + const [, itrOuter] = CancelableGenerator(itrInner, onFinally) + return itrOuter + }) - it('can cancels nested cancellable iterator in finally, asynchronously', async () => { - const onFinallyInner = jest.fn() - const waitInner = jest.fn() - const [cancelInner, itrInner] = CancelableGenerator((async function* Gen() { - yield* generate() - yield await new Promise(() => { - // should not get here - waitInner() - }) // would wait forever - }()), onFinallyInner) + it('can cancel nested cancellable iterator in finally', async () => { + const waitInner = jest.fn() + const [cancelInner, itrInner] = CancelableGenerator((async function* Gen() { + yield* generate() + yield await new Promise(() => { + // should not get here + waitInner() + }) // would wait forever + }()), onFinallyInner) + + const waitOuter = jest.fn() + const [cancelOuter, itrOuter] = CancelableGenerator((async function* Gen() { + yield* itrInner + yield await new Promise(() => { + // should not get here + waitOuter() + }) // would wait forever + }()), async () => { + await cancelInner() + await onFinally() + }) - const waitOuter = jest.fn() - const [cancelOuter, itrOuter] = CancelableGenerator((async function* Gen() { - yield* itrInner - yield await new Promise(() => { - // should not get here - waitOuter() - }) // would wait forever - }()), async () => { - await cancelInner() - await onFinally() + const received = [] + for await (const msg of itrOuter) { + received.push(msg) + if (received.length === expected.length) { + await cancelOuter() + } + } + + expect(received).toEqual(expected) + expect(waitOuter).toHaveBeenCalledTimes(0) + expect(waitInner).toHaveBeenCalledTimes(0) }) - const received = [] - for await (const msg of itrOuter) { - received.push(msg) - if (received.length === expected.length) { - setTimeout(() => { - cancelOuter() - }) + it('can cancel nested cancellable iterator in finally, asynchronously', async () => { + const waitInner = jest.fn() + const [cancelInner, itrInner] = CancelableGenerator((async function* Gen() { + yield* generate() + yield await new Promise(() => { + // should not get here + waitInner() + }) // would wait forever + }()), onFinallyInner) + + const waitOuter = jest.fn() + const [cancelOuter, itrOuter] = CancelableGenerator((async function* Gen() { + yield* itrInner + yield await new Promise(() => { + // should not get here + waitOuter() + }) // would wait forever + }()), async () => { + await cancelInner() + await onFinally() + }) + + const received = [] + for await (const msg of itrOuter) { + received.push(msg) + if (received.length === expected.length) { + setTimeout(() => { + cancelOuter() + }) + } } - } - expect(waitOuter).toHaveBeenCalledTimes(1) - expect(waitInner).toHaveBeenCalledTimes(1) - expect(received).toEqual(expected) - expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(waitOuter).toHaveBeenCalledTimes(1) + expect(waitInner).toHaveBeenCalledTimes(1) + expect(received).toEqual(expected) + }) }) it('can cancel in parallel and wait correctly for both', async () => { From ee8102c7e8fc581d3ad826c70a45fc20db979fa7 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 12 Oct 2020 14:39:44 -0400 Subject: [PATCH 091/517] Ensure iteratorFinally works with pipeline --- test/unit/iterators.test.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/unit/iterators.test.js b/test/unit/iterators.test.js index 85b692bec..254639922 100644 --- a/test/unit/iterators.test.js +++ b/test/unit/iterators.test.js @@ -1029,6 +1029,26 @@ describe('Iterator Utils', () => { expect(shouldNotGetHere).toHaveBeenCalledTimes(0) expect(received).toEqual(expected.slice(0, MAX_ITEMS)) }) + + it('runs finallyfn', async () => { + const onFinally = jest.fn() + const p = pipeline( + generate(), + async function* Step1(s) { + yield* s + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinally) + } + ) + const received = [] + for await (const msg of p) { + received.push(msg) + } + + expect(onFinally).toHaveBeenCalledTimes(1) + expect(received).toEqual(expected) + }) }) describe('stream utilities', () => { From 43139ceb7e687f2ed6efc502c82c4b73e83123c0 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 12 Oct 2020 16:05:20 -0400 Subject: [PATCH 092/517] Support nested pipelines --- src/iterators.js | 31 +++- test/unit/iterators.test.js | 283 ++++++++++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+), 3 deletions(-) diff --git a/src/iterators.js b/src/iterators.js index 739c0d136..6184b660d 100644 --- a/src/iterators.js +++ b/src/iterators.js @@ -1,4 +1,4 @@ -import { finished } from 'stream' +import { finished, Readable } from 'stream' import { promisify } from 'util' import pMemoize from 'p-memoize' @@ -278,6 +278,10 @@ export function CancelableGenerator(iterable, onFinally) { * Pipeline of async generators */ +const isPipeline = Symbol('isPipeline') + +const getIsStream = (item) => typeof item.pipe === 'function' + export function pipeline(...iterables) { const cancelFns = new Set() async function cancelAll(err) { @@ -290,11 +294,28 @@ export function pipeline(...iterables) { } } + let firstSrc + const setFirstSource = (v) => { + firstSrc = v + } + let error - const last = iterables.reduce((prev, next) => { + let first + const last = iterables.reduce((_prev, next, index) => { const [cancelCurrent, it] = CancelableGenerator((async function* Gen() { + // take first "prev" from outer iterator, if one exists + const prev = index === 0 ? firstSrc : _prev + const nextIterable = typeof next === 'function' ? next(prev) : next + + if (nextIterable[isPipeline]) { + nextIterable.setFirstSource(prev) + } + + if (prev && getIsStream(nextIterable)) { + const input = getIsStream(prev) ? prev : Readable.from(prev) + input.pipe(nextIterable) + } try { - const nextIterable = typeof next === 'function' ? next(prev) : next yield* nextIterable } catch (err) { if (!error) { @@ -304,6 +325,7 @@ export function pipeline(...iterables) { throw err } }())) + cancelFns.add(cancelCurrent) return it }, undefined) @@ -313,6 +335,9 @@ export function pipeline(...iterables) { }) return Object.assign(pipelineValue, { + first, + [isPipeline]: true, + setFirstSource, cancel: cancelAll, }) } diff --git a/test/unit/iterators.test.js b/test/unit/iterators.test.js index 254639922..d561cbc0f 100644 --- a/test/unit/iterators.test.js +++ b/test/unit/iterators.test.js @@ -1049,6 +1049,289 @@ describe('Iterator Utils', () => { expect(onFinally).toHaveBeenCalledTimes(1) expect(received).toEqual(expected) }) + + it('works with streams', async () => { + const onFinally = jest.fn() + const p = pipeline( + Readable.from(generate()), + async function* Step1(s) { + yield* s + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinally) + } + ) + const received = [] + for await (const msg of p) { + received.push(msg) + } + + expect(onFinally).toHaveBeenCalledTimes(1) + expect(received).toEqual(expected) + }) + + it('works with nested pipelines', async () => { + const onFinally = jest.fn() + const onFinallyFirst = jest.fn() + const receivedStep1 = [] + const receivedStep2 = [] + const onFirstStreamClose = jest.fn() + const onInputStreamClose = jest.fn() + const onIntermediateStreamClose = jest.fn() + + const inputStream = new PassThrough({ + objectMode: true, + }) + inputStream.once('close', onInputStreamClose) + const p1 = pipeline( + inputStream, + async function* Step1(s) { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg + } + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinallyFirst) + } + ) + + const firstStream = Readable.from(generate()) + firstStream.once('close', onFirstStreamClose) + let intermediateStream + const p = pipeline( + firstStream, + (s) => { + intermediateStream = Readable.from(s).pipe(inputStream) + intermediateStream.once('close', onIntermediateStreamClose) + return p1 + }, + async function* Step2(s) { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg + } + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinally) + } + ) + + const received = [] + for await (const msg of p) { + received.push(msg) + } + + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyFirst).toHaveBeenCalledTimes(1) + expect(received).toEqual(expected) + expect(receivedStep1).toEqual(expected) + expect(receivedStep2).toEqual(expected) + // all streams were closed + expect(onFirstStreamClose).toHaveBeenCalledTimes(1) + expect(onInputStreamClose).toHaveBeenCalledTimes(1) + expect(onIntermediateStreamClose).toHaveBeenCalledTimes(1) + }) + + it('works with streams as pipeline steps', async () => { + const onFinally = jest.fn() + const receivedStep1 = [] + const receivedStep2 = [] + const onThroughStreamClose = jest.fn() + + const throughStream = new PassThrough({ + objectMode: true, + }) + throughStream.once('close', onThroughStreamClose) + const p = pipeline( + generate(), + async function* Step1(s) { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg + } + }, + throughStream, + async function* Step2(s) { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg + } + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinally) + } + ) + + const received = [] + for await (const msg of p) { + received.push(msg) + } + + expect(received).toEqual(expected) + expect(receivedStep1).toEqual(expected) + expect(receivedStep2).toEqual(expected) + expect(onFinally).toHaveBeenCalledTimes(1) + // all streams were closed + expect(onThroughStreamClose).toHaveBeenCalledTimes(1) + }) + + it('passes outer pipeline to inner pipeline', async () => { + const onFinally = jest.fn() + const onFinallyFirst = jest.fn() + const receivedStep1 = [] + const receivedStep2 = [] + + const p1 = pipeline( + async function* Step1(s) { // s should come from outer pipeline + for await (const msg of s) { + receivedStep1.push(msg) + yield msg + } + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinallyFirst) + } + ) + + const p = pipeline( + generate(), + p1, + async function* Step2(s) { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg + } + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinally) + } + ) + + const received = [] + for await (const msg of p) { + received.push(msg) + } + + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyFirst).toHaveBeenCalledTimes(1) + expect(received).toEqual(expected) + expect(receivedStep1).toEqual(expected) + expect(receivedStep2).toEqual(expected) + // all streams were closed + }) + + it('works with nested pipelines & streams', async () => { + const onFinally = jest.fn() + const onFinallyFirst = jest.fn() + const receivedStep1 = [] + const receivedStep2 = [] + const onFirstStreamClose = jest.fn() + const onInputStreamClose = jest.fn() + + const inputStream = new PassThrough({ + objectMode: true, + }) + inputStream.once('close', onInputStreamClose) + const p1 = pipeline( + inputStream, + async function* Step1(s) { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg + } + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinallyFirst) + } + ) + + const firstStream = Readable.from(generate()) + firstStream.once('close', onFirstStreamClose) + const p = pipeline( + firstStream, + async function* Step2(s) { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg + } + }, + p1, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinally) + } + ) + + const received = [] + for await (const msg of p) { + received.push(msg) + } + + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyFirst).toHaveBeenCalledTimes(1) + expect(received).toEqual(expected) + expect(receivedStep1).toEqual(expected) + expect(receivedStep2).toEqual(expected) + // all streams were closed + expect(onFirstStreamClose).toHaveBeenCalledTimes(1) + expect(onInputStreamClose).toHaveBeenCalledTimes(1) + }) + + it('works with nested pipelines at top level', async () => { + const onFinally = jest.fn() + const onFinallyFirst = jest.fn() + const receivedStep1 = [] + const receivedStep2 = [] + const onFirstStreamClose = jest.fn() + const onInputStreamClose = jest.fn() + + const inputStream = new PassThrough({ + objectMode: true, + }) + inputStream.once('close', onInputStreamClose) + const p1 = pipeline( + inputStream, + async function* Step1(s) { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg + } + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinallyFirst) + } + ) + + const firstStream = Readable.from(generate()) + firstStream.once('close', onFirstStreamClose) + const p = pipeline( + firstStream, + async function* Step2(s) { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg + } + }, + p1, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinally) + } + ) + + const received = [] + for await (const msg of p) { + received.push(msg) + } + + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyFirst).toHaveBeenCalledTimes(1) + expect(received).toEqual(expected) + expect(receivedStep1).toEqual(expected) + expect(receivedStep2).toEqual(expected) + // all streams were closed + expect(onFirstStreamClose).toHaveBeenCalledTimes(1) + expect(onInputStreamClose).toHaveBeenCalledTimes(1) + }) }) describe('stream utilities', () => { From d80938ec14d121cf64e873d71f5f44d672affc6f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 13 Oct 2020 11:41:26 -0400 Subject: [PATCH 093/517] Add stream handling & cleanup into pipeline. --- src/iterators.js | 67 ++++++-- test/unit/iterators.test.js | 302 ++++++++++++++++++++++++++++++++++-- 2 files changed, 346 insertions(+), 23 deletions(-) diff --git a/src/iterators.js b/src/iterators.js index 6184b660d..8621e0c03 100644 --- a/src/iterators.js +++ b/src/iterators.js @@ -1,10 +1,39 @@ -import { finished, Readable } from 'stream' +import { finished, Readable, pipeline as streamPipeline } from 'stream' import { promisify } from 'util' import pMemoize from 'p-memoize' import { Defer } from './utils' +class TimeoutError extends Error { + constructor(msg = '', timeout = 0, ...args) { + super(`The operation timed out. ${timeout}ms. ${msg}`, ...args) + this.timeout = timeout + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor) + } + } +} + +async function pTimeout(promise, timeout, message = '') { + let t + return Promise.race([ + promise, + new Promise((resolve, reject) => { + t = setTimeout(() => { + reject(new TimeoutError(message, timeout)) + }) + }) + ]).finally(() => { + clearTimeout(t) + }) +} + +pTimeout.ignoreError = (err) => { + if (err instanceof TimeoutError) { return } + throw err +} + export class AbortError extends Error { constructor(msg = '', ...args) { super(`The operation was aborted. ${msg}`, ...args) @@ -23,7 +52,7 @@ export async function endStream(stream, optionalErr) { await pFinished(stream) } catch (err) { if (err.code === 'ERR_STREAM_PREMATURE_CLOSE') { - await pFinished(stream) + await pFinished(stream, optionalErr) } } } @@ -143,7 +172,7 @@ export function iteratorFinally(iterable, onFinally) { * const [cancal, generator] = CancelableGenerator(iterable, onFinally) */ -export function CancelableGenerator(iterable, onFinally) { +export function CancelableGenerator(iterable, onFinally, { timeout = 1000 } = {}) { let started = false let cancelled = false let finalCalled = false @@ -177,10 +206,10 @@ export function CancelableGenerator(iterable, onFinally) { ? gtr.throw(error).catch(() => {}) : gtr.return() - // wait for generator if it didn't start + // wait for generator if it didn't start or there's a timeout // i.e. wait for finally - if (!started) { - await onGenEnd + if (!started || timeout) { + await pTimeout(onGenEnd, timeout).catch(pTimeout.ignoreError) return onDone } } @@ -232,10 +261,10 @@ export function CancelableGenerator(iterable, onFinally) { } finally { // try end iterator if (iterator) { - if (pendingNextCount) { - iterator.return() + if (!pendingNextCount || timeout) { + await pTimeout(iterator.return(), timeout).catch(pTimeout.ignoreError) } else { - await iterator.return() + iterator.return() } } } @@ -302,29 +331,41 @@ export function pipeline(...iterables) { let error let first const last = iterables.reduce((_prev, next, index) => { + let stream + const [cancelCurrent, it] = CancelableGenerator((async function* Gen() { // take first "prev" from outer iterator, if one exists const prev = index === 0 ? firstSrc : _prev - const nextIterable = typeof next === 'function' ? next(prev) : next + let nextIterable = typeof next === 'function' ? next(prev) : next if (nextIterable[isPipeline]) { nextIterable.setFirstSource(prev) } + if (nextIterable && getIsStream(nextIterable)) { + stream = nextIterable + } + if (prev && getIsStream(nextIterable)) { const input = getIsStream(prev) ? prev : Readable.from(prev) - input.pipe(nextIterable) + nextIterable = streamPipeline(input, nextIterable, () => { + // ignore error + }) } try { yield* nextIterable } catch (err) { if (!error) { error = err - cancelAll(err) + await cancelAll(err) } throw err } - }())) + }()), async (err) => { + if (stream) { + await endStream(stream, err) + } + }) cancelFns.add(cancelCurrent) return it diff --git a/test/unit/iterators.test.js b/test/unit/iterators.test.js index d561cbc0f..187ef4818 100644 --- a/test/unit/iterators.test.js +++ b/test/unit/iterators.test.js @@ -1,4 +1,4 @@ -import { Readable, PassThrough } from 'stream' +import { Readable, PassThrough, finished } from 'stream' import Debug from 'debug' import { wait } from 'streamr-test-utils' @@ -22,7 +22,7 @@ async function* generate(items = expected) { await wait(WAIT * 0.1) } -const MAX_ITEMS = 2 +const MAX_ITEMS = 3 function IteratorTest(name, fn) { describe(`${name} IteratorTest`, () => { @@ -80,6 +80,25 @@ function IteratorTest(name, fn) { expect(received).toEqual(expected.slice(0, MAX_ITEMS)) }) + it('throws parent mid-iteration', async () => { + const received = [] + const err = new Error('expected err') + async function* parentGen() { + for await (const msg of fn()) { + yield msg + if (received.length === MAX_ITEMS) { + throw err + } + } + } + await expect(async () => { + for await (const msg of parentGen()) { + received.push(msg) + } + }).rejects.toThrow(err) + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + }) + it('can throw before iterating', async () => { const received = [] const itr = fn()[Symbol.asyncIterator]() @@ -145,21 +164,21 @@ describe('Iterator Utils', () => { it('runs fn when iterator.return() is called asynchronously', async () => { const received = [] const itr = iteratorFinally(generate(), onFinally) + const onTimeoutReached = jest.fn() let receievedAtCallTime for await (const msg of itr) { received.push(msg) if (received.length === MAX_ITEMS) { // eslint-disable-next-line no-loop-func setTimeout(() => { + onTimeoutReached() receievedAtCallTime = received itr.return() }) } - - setTimeout(() => { - itr.return() - }) } + + expect(onTimeoutReached).toHaveBeenCalledTimes(1) expect(received).toEqual(receievedAtCallTime) }) @@ -178,7 +197,7 @@ describe('Iterator Utils', () => { it('does not call inner iterators onFinally with error if outer errors', async () => { // maybe not desirable, but captures existing behaviour. - // this matches native generator/iterator behaviour, only outermost iteration actually errors + // this matches native generator/iterator behaviour, at most outermost iteration errors const received = [] const err = new Error('expected err') const itr = iteratorFinally(generate(), onFinally) @@ -208,7 +227,7 @@ describe('Iterator Utils', () => { } }).rejects.toThrow(err) expect(received).toEqual(expected.slice(0, MAX_ITEMS)) - expect(onFinally).not.toHaveBeenCalledWith(err) // only outer onFinally will have err + expect(onFinally).not.toHaveBeenCalledWith(err) // just outer onFinally will have err }) it('runs fn when iterator returns before iteration', async () => { @@ -997,7 +1016,7 @@ describe('Iterator Utils', () => { expect(received).toEqual(expected) }) - it('handles errors after', async () => { + it('handles cancel with error after', async () => { const err = new Error('expected') const receivedStep2 = [] const shouldNotGetHere = jest.fn() @@ -1013,7 +1032,6 @@ describe('Iterator Utils', () => { yield msg if (receivedStep2.length === MAX_ITEMS) { await p.cancel(err) - shouldNotGetHere() } } } @@ -1133,16 +1151,71 @@ describe('Iterator Utils', () => { expect(onIntermediateStreamClose).toHaveBeenCalledTimes(1) }) + it('works with nested pipelines that throw', async () => { + const onFinally = jest.fn() + const onFinallyFirst = jest.fn() + const receivedStep1 = [] + const receivedStep2 = [] + + const err = new Error('expected err') + const p1 = pipeline( + async function* Step2(s) { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg + if (receivedStep2.length === MAX_ITEMS) { + throw err + } + } + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinallyFirst) + } + ) + + const p = pipeline( + generate(), + async function* Step1(s) { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg + } + }, + p1, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinally) + } + ) + + const received = [] + await expect(async () => { + for await (const msg of p) { + received.push(msg) + } + }).rejects.toThrow(err) + + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyFirst).toHaveBeenCalledTimes(1) + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + expect(receivedStep1).toEqual(received) + expect(receivedStep2).toEqual(received) + }) + it('works with streams as pipeline steps', async () => { const onFinally = jest.fn() const receivedStep1 = [] const receivedStep2 = [] const onThroughStreamClose = jest.fn() + const onThroughStream2Close = jest.fn() const throughStream = new PassThrough({ objectMode: true, }) throughStream.once('close', onThroughStreamClose) + const throughStream2 = new PassThrough({ + objectMode: true, + }) + throughStream2.once('close', onThroughStream2Close) const p = pipeline( generate(), async function* Step1(s) { @@ -1158,6 +1231,7 @@ describe('Iterator Utils', () => { yield msg } }, + throughStream2, async function* finallyFn(s) { yield* iteratorFinally(s, onFinally) } @@ -1174,6 +1248,153 @@ describe('Iterator Utils', () => { expect(onFinally).toHaveBeenCalledTimes(1) // all streams were closed expect(onThroughStreamClose).toHaveBeenCalledTimes(1) + expect(onThroughStream2Close).toHaveBeenCalledTimes(1) + }) + + it('works with streams as pipeline steps with early return', async () => { + const onFinally = jest.fn() + const receivedStep1 = [] + const onThroughStreamClose = jest.fn() + const onThroughStream2Close = jest.fn() + + const throughStream = new PassThrough({ + objectMode: true, + }) + throughStream.once('close', onThroughStreamClose) + const throughStream2 = new PassThrough({ + objectMode: true, + }) + throughStream2.once('close', onThroughStream2Close) + const p = pipeline( + generate(), + throughStream, + async function* Step1(s) { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg + if (receivedStep1.length === MAX_ITEMS) { + break + } + } + }, + throughStream2, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinally) + } + ) + + const received = [] + for await (const msg of p) { + received.push(msg) + } + + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + expect(receivedStep1).toEqual(expected.slice(0, MAX_ITEMS)) + + expect(onFinally).toHaveBeenCalledTimes(1) + // all streams were closed + expect(onThroughStreamClose).toHaveBeenCalledTimes(1) + expect(onThroughStream2Close).toHaveBeenCalledTimes(1) + }) + + it('works with streams as pipeline steps with throw', async () => { + const onFinally = jest.fn() + const receivedStep1 = [] + const onThroughStreamClose = jest.fn() + + const throughStream = new PassThrough({ + objectMode: true, + }) + throughStream.once('close', onThroughStreamClose) + const err = new Error('expected err') + const p = pipeline( + generate(), + throughStream, + async function* Step1(s) { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg + if (receivedStep1.length === MAX_ITEMS) { + throw err + } + } + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinally) + } + ) + + const received = [] + await expect(async () => { + for await (const msg of p) { + received.push(msg) + } + }).rejects.toThrow(err) + + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + expect(receivedStep1).toEqual(received) + + expect(onFinally).toHaveBeenCalledTimes(1) + // all streams were closed + expect(onThroughStreamClose).toHaveBeenCalledTimes(1) + }) + + it('works with multiple streams as pipeline steps with throw', async () => { + const onFinally = jest.fn() + const receivedStep1 = [] + const receivedStep2 = [] + const onThroughStreamClose = jest.fn() + const onThroughStream2Close = jest.fn() + + const throughStream = new PassThrough({ + objectMode: true, + }) + + const throughStream2 = new PassThrough({ + objectMode: true, + }) + throughStream.once('close', onThroughStreamClose) + const err = new Error('expected err') + throughStream2.once('close', onThroughStream2Close) + const p = pipeline( + generate(), + throughStream, + async function* Step1(s) { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg + } + }, + throughStream2, + async function* Step2(s) { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg + if (receivedStep2.length === MAX_ITEMS) { + throw err + } + } + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinally) + } + ) + + const received = [] + await expect(async () => { + for await (const msg of p) { + received.push(msg) + } + }).rejects.toThrow(err) + + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + expect(receivedStep1).toEqual(received) + expect(receivedStep2).toEqual(received) + + expect(onFinally).toHaveBeenCalledTimes(1) + // all streams were closed + expect(onThroughStream2Close).toHaveBeenCalledTimes(1) + expect(onThroughStreamClose).toHaveBeenCalledTimes(1) }) it('passes outer pipeline to inner pipeline', async () => { @@ -1277,6 +1498,67 @@ describe('Iterator Utils', () => { expect(onInputStreamClose).toHaveBeenCalledTimes(1) }) + it('works with nested pipelines & streams closing before done', async () => { + const onFinally = jest.fn() + const onFinallyFirst = jest.fn() + const receivedStep1 = [] + const receivedStep2 = [] + const onFirstStreamClose = jest.fn() + const onInputStreamClose = jest.fn() + + const inputStream = new PassThrough({ + objectMode: true, + }) + inputStream.once('close', onInputStreamClose) + const p1 = pipeline( + inputStream, + async function* Step1(s) { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg + if (receivedStep1.length === MAX_ITEMS) { + break + } + } + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinallyFirst) + } + ) + + const firstStream = Readable.from(generate()) + firstStream.once('close', onFirstStreamClose) + const p = pipeline( + firstStream, + p1, + async function* Step2(s) { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg + } + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinally) + } + ) + + const received = [] + for await (const msg of p) { + received.push(msg) + } + + await wait(1000) + + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + expect(receivedStep1).toEqual(expected.slice(0, MAX_ITEMS)) + expect(receivedStep2).toEqual(expected.slice(0, MAX_ITEMS)) + // all streams were closed + expect(onFirstStreamClose).toHaveBeenCalledTimes(1) + expect(onInputStreamClose).toHaveBeenCalledTimes(1) + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyFirst).toHaveBeenCalledTimes(1) + }) + it('works with nested pipelines at top level', async () => { const onFinally = jest.fn() const onFinallyFirst = jest.fn() From c8c7f4d29141dff8e777e63920b63945eaabb6be Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 13 Oct 2020 13:23:55 -0400 Subject: [PATCH 094/517] Add cleanup function support to pipeline. --- src/iterators.js | 42 +++-- test/unit/iterators.test.js | 326 ++++++++++++++++++++---------------- 2 files changed, 217 insertions(+), 151 deletions(-) diff --git a/src/iterators.js b/src/iterators.js index 8621e0c03..5c9d7eef4 100644 --- a/src/iterators.js +++ b/src/iterators.js @@ -147,6 +147,10 @@ export function iteratorFinally(iterable, onFinally) { }()) const it = g[Symbol.asyncIterator].bind(g) + if (iterable.cancel) { + g.cancel = (...args) => iterable.cancel(...args) + } + // replace generator methods return Object.assign(g, { return: handleFinally(g.return.bind(g)), @@ -311,13 +315,24 @@ const isPipeline = Symbol('isPipeline') const getIsStream = (item) => typeof item.pipe === 'function' -export function pipeline(...iterables) { +export function pipeline(iterables = [], onFinally) { const cancelFns = new Set() - async function cancelAll(err) { + let cancelled = false + let error + const onCancelDone = Defer() + const cancelAll = async (err) => { + if (cancelled) { + await onCancelDone + return + } + + cancelled = true + error = err try { - await allSettledValues([...cancelFns].map(async (cancel) => ( + // eslint-disable-next-line promise/no-promise-in-callback + await Promise.all([...cancelFns].map(async (cancel) => ( cancel(err) - ))) + ))).then(onCancelDone.resolve, onCancelDone.reject) } finally { cancelFns.clear() } @@ -328,8 +343,6 @@ export function pipeline(...iterables) { firstSrc = v } - let error - let first const last = iterables.reduce((_prev, next, index) => { let stream @@ -339,6 +352,7 @@ export function pipeline(...iterables) { let nextIterable = typeof next === 'function' ? next(prev) : next if (nextIterable[isPipeline]) { + cancelFns.add(nextIterable.cancel) nextIterable.setFirstSource(prev) } @@ -355,15 +369,18 @@ export function pipeline(...iterables) { try { yield* nextIterable } catch (err) { - if (!error) { + if (!error && err && error !== err) { error = err - await cancelAll(err) } throw err } }()), async (err) => { + if (!cancelled && err) { + await cancelAll(err || error) + } + if (stream) { - await endStream(stream, err) + await endStream(stream, err || error) } }) @@ -371,12 +388,15 @@ export function pipeline(...iterables) { return it }, undefined) - const pipelineValue = iteratorFinally(last, () => { + const pipelineValue = iteratorFinally(last, async () => { + if (!cancelled) { + await cancelAll(error) + } cancelFns.clear() + await onFinally(error) }) return Object.assign(pipelineValue, { - first, [isPipeline]: true, setFirstSource, cancel: cancelAll, diff --git a/test/unit/iterators.test.js b/test/unit/iterators.test.js index 187ef4818..c5d8735bb 100644 --- a/test/unit/iterators.test.js +++ b/test/unit/iterators.test.js @@ -771,9 +771,25 @@ describe('Iterator Utils', () => { }) describe('pipeline', () => { + let onFinally + let onFinallyAfter + + beforeEach(() => { + onFinallyAfter = jest.fn() + onFinally = jest.fn(async () => { + await wait(WAIT) + onFinallyAfter() + }) + }) + + afterEach(() => { + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) + }) + describe('baseline', () => { IteratorTest('pipeline', () => { - return pipeline( + return pipeline([ generate(), async function* Step1(s) { for await (const msg of s) { @@ -785,7 +801,7 @@ describe('Iterator Utils', () => { yield msg / 2 } } - ) + ], onFinally) }) }) @@ -795,7 +811,7 @@ describe('Iterator Utils', () => { const afterStep1 = jest.fn() const afterStep2 = jest.fn() - const p = pipeline( + const p = pipeline([ generate(), async function* Step1(s) { try { @@ -819,7 +835,7 @@ describe('Iterator Utils', () => { afterStep2() } } - ) + ], onFinally) const received = [] for await (const msg of p) { @@ -838,7 +854,7 @@ describe('Iterator Utils', () => { const receivedStep2 = [] const afterStep1 = jest.fn() const afterStep2 = jest.fn() - const p = pipeline( + const p = pipeline([ expected, async function* Step1(s) { try { @@ -860,7 +876,7 @@ describe('Iterator Utils', () => { afterStep2() } } - ) + ], onFinally) const received = [] for await (const msg of p) { @@ -880,7 +896,7 @@ describe('Iterator Utils', () => { const afterStep1 = jest.fn() const afterStep2 = jest.fn() - const p = pipeline( + const p = pipeline([ generate(), async function* Step1(s) { try { @@ -905,7 +921,7 @@ describe('Iterator Utils', () => { afterStep2() } } - ) + ], onFinally) const received = [] for await (const msg of p) { @@ -926,7 +942,7 @@ describe('Iterator Utils', () => { const afterStep2 = jest.fn() const err = new Error('expected') - const p = pipeline( + const p = pipeline([ generate(), async function* Step1(s) { try { @@ -951,7 +967,7 @@ describe('Iterator Utils', () => { afterStep2() } } - ) + ], onFinally) const received = [] await expect(async () => { @@ -970,7 +986,7 @@ describe('Iterator Utils', () => { it('handles errors before', async () => { const err = new Error('expected') - const p = pipeline( + const p = pipeline([ generate(), async function* Step1(s) { yield* s @@ -980,7 +996,7 @@ describe('Iterator Utils', () => { yield* s yield await new Promise(() => {}) // would wait forever } - ) + ], onFinally) const received = [] await expect(async () => { @@ -995,7 +1011,7 @@ describe('Iterator Utils', () => { it('handles errors after', async () => { const err = new Error('expected') - const p = pipeline( + const p = pipeline([ generate(), async function* Step1(s) { yield* s @@ -1004,7 +1020,7 @@ describe('Iterator Utils', () => { yield* s throw err } - ) + ], onFinally) const received = [] await expect(async () => { @@ -1021,7 +1037,7 @@ describe('Iterator Utils', () => { const receivedStep2 = [] const shouldNotGetHere = jest.fn() - const p = pipeline( + const p = pipeline([ generate(), async function* Step1(s) { yield* s @@ -1035,7 +1051,7 @@ describe('Iterator Utils', () => { } } } - ) + ], onFinally) const received = [] await expect(async () => { @@ -1048,49 +1064,114 @@ describe('Iterator Utils', () => { expect(received).toEqual(expected.slice(0, MAX_ITEMS)) }) - it('runs finallyfn', async () => { - const onFinally = jest.fn() - const p = pipeline( + it('runs onFinally', async () => { + const onFinallyInnerAfter = jest.fn() + const onFinallyInner = jest.fn(async () => { + await wait(WAIT) + onFinallyInnerAfter() + }) + const p = pipeline([ generate(), async function* Step1(s) { yield* s }, async function* finallyFn(s) { - yield* iteratorFinally(s, onFinally) + yield* iteratorFinally(s, onFinallyInner) } - ) + ], onFinally) const received = [] for await (const msg of p) { received.push(msg) } - expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) expect(received).toEqual(expected) }) + it('runs onFinally even if not started', async () => { + const onFinallyInnerAfter = jest.fn() + const onFinallyInner = jest.fn(async () => { + await wait(WAIT) + onFinallyInnerAfter() + }) + const p = pipeline([ + generate(), + async function* Step1(s) { + yield* s + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinallyInner) + } + ], onFinally) + await p.return() + const received = [] + for await (const msg of p) { + received.push(msg) + } + + expect(onFinallyInner).toHaveBeenCalledTimes(0) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(0) + expect(received).toEqual([]) + }) + + it('runs onFinally even if not started when cancelled', async () => { + const onFinallyInnerAfter = jest.fn() + const onFinallyInner = jest.fn(async () => { + await wait(WAIT) + onFinallyInnerAfter() + }) + const p = pipeline([ + generate(), + async function* Step1(s) { + yield* s + }, + async function* finallyFn(s) { + yield* iteratorFinally(s, onFinallyInner) + } + ], onFinally) + await p.cancel() + const received = [] + for await (const msg of p) { + received.push(msg) + } + + expect(onFinallyInner).toHaveBeenCalledTimes(0) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(0) + expect(received).toEqual([]) + }) + it('works with streams', async () => { - const onFinally = jest.fn() - const p = pipeline( + const onFinallyInnerAfter = jest.fn() + const onFinallyInner = jest.fn(async () => { + await wait(WAIT) + onFinallyInnerAfter() + }) + const p = pipeline([ Readable.from(generate()), async function* Step1(s) { yield* s }, async function* finallyFn(s) { - yield* iteratorFinally(s, onFinally) + yield* iteratorFinally(s, onFinallyInner) } - ) + ], onFinally) const received = [] for await (const msg of p) { received.push(msg) } - expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) expect(received).toEqual(expected) }) it('works with nested pipelines', async () => { - const onFinally = jest.fn() - const onFinallyFirst = jest.fn() + const onFinallyInnerAfter = jest.fn() + const onFinallyInner = jest.fn(async () => { + await wait(WAIT) + onFinallyInnerAfter() + }) const receivedStep1 = [] const receivedStep2 = [] const onFirstStreamClose = jest.fn() @@ -1101,7 +1182,7 @@ describe('Iterator Utils', () => { objectMode: true, }) inputStream.once('close', onInputStreamClose) - const p1 = pipeline( + const p1 = pipeline([ inputStream, async function* Step1(s) { for await (const msg of s) { @@ -1109,15 +1190,12 @@ describe('Iterator Utils', () => { yield msg } }, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinallyFirst) - } - ) + ], onFinallyInner) const firstStream = Readable.from(generate()) firstStream.once('close', onFirstStreamClose) let intermediateStream - const p = pipeline( + const p = pipeline([ firstStream, (s) => { intermediateStream = Readable.from(s).pipe(inputStream) @@ -1130,21 +1208,20 @@ describe('Iterator Utils', () => { yield msg } }, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinally) - } - ) + ], onFinally) const received = [] for await (const msg of p) { received.push(msg) } - expect(onFinally).toHaveBeenCalledTimes(1) - expect(onFinallyFirst).toHaveBeenCalledTimes(1) + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) + expect(received).toEqual(expected) expect(receivedStep1).toEqual(expected) expect(receivedStep2).toEqual(expected) + // all streams were closed expect(onFirstStreamClose).toHaveBeenCalledTimes(1) expect(onInputStreamClose).toHaveBeenCalledTimes(1) @@ -1152,13 +1229,17 @@ describe('Iterator Utils', () => { }) it('works with nested pipelines that throw', async () => { - const onFinally = jest.fn() - const onFinallyFirst = jest.fn() + const onFinallyInnerAfter = jest.fn() + const onFinallyInner = jest.fn(async () => { + await wait(WAIT) + onFinallyInnerAfter() + }) + const receivedStep1 = [] const receivedStep2 = [] const err = new Error('expected err') - const p1 = pipeline( + const p1 = pipeline([ async function* Step2(s) { for await (const msg of s) { receivedStep2.push(msg) @@ -1168,12 +1249,9 @@ describe('Iterator Utils', () => { } } }, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinallyFirst) - } - ) + ], onFinallyInner) - const p = pipeline( + const p = pipeline([ generate(), async function* Step1(s) { for await (const msg of s) { @@ -1181,11 +1259,8 @@ describe('Iterator Utils', () => { yield msg } }, - p1, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinally) - } - ) + p1 + ], onFinally) const received = [] await expect(async () => { @@ -1194,15 +1269,15 @@ describe('Iterator Utils', () => { } }).rejects.toThrow(err) - expect(onFinally).toHaveBeenCalledTimes(1) - expect(onFinallyFirst).toHaveBeenCalledTimes(1) + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) expect(receivedStep1).toEqual(received) expect(receivedStep2).toEqual(received) }) it('works with streams as pipeline steps', async () => { - const onFinally = jest.fn() const receivedStep1 = [] const receivedStep2 = [] const onThroughStreamClose = jest.fn() @@ -1216,7 +1291,7 @@ describe('Iterator Utils', () => { objectMode: true, }) throughStream2.once('close', onThroughStream2Close) - const p = pipeline( + const p = pipeline([ generate(), async function* Step1(s) { for await (const msg of s) { @@ -1232,10 +1307,7 @@ describe('Iterator Utils', () => { } }, throughStream2, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinally) - } - ) + ], onFinally) const received = [] for await (const msg of p) { @@ -1245,14 +1317,12 @@ describe('Iterator Utils', () => { expect(received).toEqual(expected) expect(receivedStep1).toEqual(expected) expect(receivedStep2).toEqual(expected) - expect(onFinally).toHaveBeenCalledTimes(1) // all streams were closed expect(onThroughStreamClose).toHaveBeenCalledTimes(1) expect(onThroughStream2Close).toHaveBeenCalledTimes(1) }) it('works with streams as pipeline steps with early return', async () => { - const onFinally = jest.fn() const receivedStep1 = [] const onThroughStreamClose = jest.fn() const onThroughStream2Close = jest.fn() @@ -1265,7 +1335,7 @@ describe('Iterator Utils', () => { objectMode: true, }) throughStream2.once('close', onThroughStream2Close) - const p = pipeline( + const p = pipeline([ generate(), throughStream, async function* Step1(s) { @@ -1278,10 +1348,7 @@ describe('Iterator Utils', () => { } }, throughStream2, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinally) - } - ) + ], onFinally) const received = [] for await (const msg of p) { @@ -1291,14 +1358,12 @@ describe('Iterator Utils', () => { expect(received).toEqual(expected.slice(0, MAX_ITEMS)) expect(receivedStep1).toEqual(expected.slice(0, MAX_ITEMS)) - expect(onFinally).toHaveBeenCalledTimes(1) // all streams were closed expect(onThroughStreamClose).toHaveBeenCalledTimes(1) expect(onThroughStream2Close).toHaveBeenCalledTimes(1) }) it('works with streams as pipeline steps with throw', async () => { - const onFinally = jest.fn() const receivedStep1 = [] const onThroughStreamClose = jest.fn() @@ -1307,7 +1372,7 @@ describe('Iterator Utils', () => { }) throughStream.once('close', onThroughStreamClose) const err = new Error('expected err') - const p = pipeline( + const p = pipeline([ generate(), throughStream, async function* Step1(s) { @@ -1319,10 +1384,7 @@ describe('Iterator Utils', () => { } } }, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinally) - } - ) + ], onFinally) const received = [] await expect(async () => { @@ -1334,13 +1396,11 @@ describe('Iterator Utils', () => { expect(received).toEqual(expected.slice(0, MAX_ITEMS)) expect(receivedStep1).toEqual(received) - expect(onFinally).toHaveBeenCalledTimes(1) // all streams were closed expect(onThroughStreamClose).toHaveBeenCalledTimes(1) }) it('works with multiple streams as pipeline steps with throw', async () => { - const onFinally = jest.fn() const receivedStep1 = [] const receivedStep2 = [] const onThroughStreamClose = jest.fn() @@ -1356,7 +1416,7 @@ describe('Iterator Utils', () => { throughStream.once('close', onThroughStreamClose) const err = new Error('expected err') throughStream2.once('close', onThroughStream2Close) - const p = pipeline( + const p = pipeline([ generate(), throughStream, async function* Step1(s) { @@ -1375,10 +1435,7 @@ describe('Iterator Utils', () => { } } }, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinally) - } - ) + ], onFinally) const received = [] await expect(async () => { @@ -1391,31 +1448,30 @@ describe('Iterator Utils', () => { expect(receivedStep1).toEqual(received) expect(receivedStep2).toEqual(received) - expect(onFinally).toHaveBeenCalledTimes(1) // all streams were closed expect(onThroughStream2Close).toHaveBeenCalledTimes(1) expect(onThroughStreamClose).toHaveBeenCalledTimes(1) }) it('passes outer pipeline to inner pipeline', async () => { - const onFinally = jest.fn() - const onFinallyFirst = jest.fn() + const onFinallyInnerAfter = jest.fn() + const onFinallyInner = jest.fn(async () => { + await wait(WAIT) + onFinallyInnerAfter() + }) const receivedStep1 = [] const receivedStep2 = [] - const p1 = pipeline( + const p1 = pipeline([ async function* Step1(s) { // s should come from outer pipeline for await (const msg of s) { receivedStep1.push(msg) yield msg } }, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinallyFirst) - } - ) + ], onFinallyInner) - const p = pipeline( + const p = pipeline([ generate(), p1, async function* Step2(s) { @@ -1424,18 +1480,15 @@ describe('Iterator Utils', () => { yield msg } }, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinally) - } - ) + ], onFinally) const received = [] for await (const msg of p) { received.push(msg) } - expect(onFinally).toHaveBeenCalledTimes(1) - expect(onFinallyFirst).toHaveBeenCalledTimes(1) + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) expect(received).toEqual(expected) expect(receivedStep1).toEqual(expected) expect(receivedStep2).toEqual(expected) @@ -1443,8 +1496,11 @@ describe('Iterator Utils', () => { }) it('works with nested pipelines & streams', async () => { - const onFinally = jest.fn() - const onFinallyFirst = jest.fn() + const onFinallyInnerAfter = jest.fn() + const onFinallyInner = jest.fn(async () => { + await wait(WAIT) + onFinallyInnerAfter() + }) const receivedStep1 = [] const receivedStep2 = [] const onFirstStreamClose = jest.fn() @@ -1454,7 +1510,7 @@ describe('Iterator Utils', () => { objectMode: true, }) inputStream.once('close', onInputStreamClose) - const p1 = pipeline( + const p1 = pipeline([ inputStream, async function* Step1(s) { for await (const msg of s) { @@ -1462,14 +1518,11 @@ describe('Iterator Utils', () => { yield msg } }, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinallyFirst) - } - ) + ], onFinallyInner) const firstStream = Readable.from(generate()) firstStream.once('close', onFirstStreamClose) - const p = pipeline( + const p = pipeline([ firstStream, async function* Step2(s) { for await (const msg of s) { @@ -1478,18 +1531,15 @@ describe('Iterator Utils', () => { } }, p1, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinally) - } - ) + ], onFinally) const received = [] for await (const msg of p) { received.push(msg) } - expect(onFinally).toHaveBeenCalledTimes(1) - expect(onFinallyFirst).toHaveBeenCalledTimes(1) + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) expect(received).toEqual(expected) expect(receivedStep1).toEqual(expected) expect(receivedStep2).toEqual(expected) @@ -1499,8 +1549,11 @@ describe('Iterator Utils', () => { }) it('works with nested pipelines & streams closing before done', async () => { - const onFinally = jest.fn() - const onFinallyFirst = jest.fn() + const onFinallyInnerAfter = jest.fn() + const onFinallyInner = jest.fn(async () => { + await wait(WAIT) + onFinallyInnerAfter() + }) const receivedStep1 = [] const receivedStep2 = [] const onFirstStreamClose = jest.fn() @@ -1510,7 +1563,7 @@ describe('Iterator Utils', () => { objectMode: true, }) inputStream.once('close', onInputStreamClose) - const p1 = pipeline( + const p1 = pipeline([ inputStream, async function* Step1(s) { for await (const msg of s) { @@ -1521,14 +1574,11 @@ describe('Iterator Utils', () => { } } }, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinallyFirst) - } - ) + ], onFinallyInner) const firstStream = Readable.from(generate()) firstStream.once('close', onFirstStreamClose) - const p = pipeline( + const p = pipeline([ firstStream, p1, async function* Step2(s) { @@ -1537,10 +1587,7 @@ describe('Iterator Utils', () => { yield msg } }, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinally) - } - ) + ], onFinally) const received = [] for await (const msg of p) { @@ -1555,13 +1602,18 @@ describe('Iterator Utils', () => { // all streams were closed expect(onFirstStreamClose).toHaveBeenCalledTimes(1) expect(onInputStreamClose).toHaveBeenCalledTimes(1) - expect(onFinally).toHaveBeenCalledTimes(1) - expect(onFinallyFirst).toHaveBeenCalledTimes(1) + + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) }) it('works with nested pipelines at top level', async () => { - const onFinally = jest.fn() - const onFinallyFirst = jest.fn() + const onFinallyInnerAfter = jest.fn() + const onFinallyInner = jest.fn(async () => { + await wait(WAIT) + onFinallyInnerAfter() + }) + const receivedStep1 = [] const receivedStep2 = [] const onFirstStreamClose = jest.fn() @@ -1571,7 +1623,7 @@ describe('Iterator Utils', () => { objectMode: true, }) inputStream.once('close', onInputStreamClose) - const p1 = pipeline( + const p1 = pipeline([ inputStream, async function* Step1(s) { for await (const msg of s) { @@ -1579,14 +1631,11 @@ describe('Iterator Utils', () => { yield msg } }, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinallyFirst) - } - ) + ], onFinallyInner) const firstStream = Readable.from(generate()) firstStream.once('close', onFirstStreamClose) - const p = pipeline( + const p = pipeline([ firstStream, async function* Step2(s) { for await (const msg of s) { @@ -1595,18 +1644,15 @@ describe('Iterator Utils', () => { } }, p1, - async function* finallyFn(s) { - yield* iteratorFinally(s, onFinally) - } - ) + ], onFinally) const received = [] for await (const msg of p) { received.push(msg) } - expect(onFinally).toHaveBeenCalledTimes(1) - expect(onFinallyFirst).toHaveBeenCalledTimes(1) + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) expect(received).toEqual(expected) expect(receivedStep1).toEqual(expected) expect(receivedStep2).toEqual(expected) From 27acb7b69b7e5f84c11dbf5340aa653e3e175fc6 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 13 Oct 2020 17:33:17 -0400 Subject: [PATCH 095/517] Subscriptions+resend functional when backend plays nice. --- package-lock.json | 1526 +++++++++++++++++++++++- package.json | 3 + src/Stream.js | 372 +++--- src/Subscriber.js | 49 +- src/iterators.js | 54 +- src/rest/authFetch.js | 7 +- src/utils.js | 20 + test/integration/Stream.test.js | 184 ++- test/integration/StreamResends.test.js | 51 +- test/unit/iterators.test.js | 277 ++++- webpack.config.js | 3 + 11 files changed, 2177 insertions(+), 369 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4016648ec..afac55480 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4175,6 +4175,23 @@ "inherits": "^2.0.3", "readable-stream": "^2.2.2", "typedarray": "^0.0.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } }, "confusing-browser-globals": { @@ -4473,6 +4490,12 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, + "dedent": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", + "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", + "dev": true + }, "deep-eql": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-0.1.3.tgz", @@ -4721,6 +4744,23 @@ "dev": true, "requires": { "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } }, "duplexify": { @@ -4733,6 +4773,23 @@ "inherits": "^2.0.1", "readable-stream": "^2.0.0", "stream-shift": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } }, "ecc-jsbn": { @@ -4829,6 +4886,23 @@ "requires": { "errno": "^0.1.3", "readable-stream": "^2.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } } } @@ -6154,6 +6228,23 @@ "requires": { "inherits": "^2.0.3", "readable-stream": "^2.3.6" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } }, "follow-redirects": { @@ -6234,6 +6325,23 @@ "requires": { "inherits": "^2.0.1", "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } }, "fs-extra": { @@ -6272,6 +6380,23 @@ "iferr": "^0.1.5", "imurmurhash": "^0.1.4", "readable-stream": "1 || 2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } }, "fs.realpath": { @@ -6414,6 +6539,21 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } } } }, @@ -6576,6 +6716,23 @@ "timed-out": "^2.0.0", "unzip-response": "^1.0.0", "url-parse-lax": "^1.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } }, "graceful-fs": { @@ -7512,31 +7669,866 @@ "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + } + } + }, + "jest-circus": { + "version": "26.5.1", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-26.5.1.tgz", + "integrity": "sha512-8tXACLNh1FVGl6tDvu5d6rWVZ0N4peOL3/QlaqZme7a3DtprmjFG6NCQQnLgRYRsv2f5p12OvxVNekRnidNrYw==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.5.0", + "@jest/test-result": "^26.5.0", + "@jest/types": "^26.5.0", + "@types/babel__traverse": "^7.0.4", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "dedent": "^0.7.0", + "expect": "^26.5.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.5.0", + "jest-matcher-utils": "^26.5.0", + "jest-message-util": "^26.5.0", + "jest-runner": "^26.5.1", + "jest-runtime": "^26.5.0", + "jest-snapshot": "^26.5.0", + "jest-util": "^26.5.0", + "pretty-format": "^26.5.0", + "stack-utils": "^2.0.2", + "throat": "^5.0.0" + }, + "dependencies": { + "@jest/console": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.5.0.tgz", + "integrity": "sha512-oh59scth4yf8XUgMJb8ruY7BHm0X5JZDNgGGsVnlOt2XQuq9s2NMllIrN4n70Yds+++bjrTGZ9EoOKraaPKPlg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "jest-message-util": "^26.5.0", + "jest-util": "^26.5.0", + "slash": "^3.0.0" + } + }, + "@jest/environment": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.5.0.tgz", + "integrity": "sha512-0F3G9EyZU2NAP0/c/5EqVx4DmldQtRxj0gMl3p3ciSCdyMiCyDmpdE7O0mKTSiFDyl1kU4TfgEVf0r0vMkmYcw==", + "dev": true, + "requires": { + "@jest/fake-timers": "^26.5.0", + "@jest/types": "^26.5.0", + "@types/node": "*", + "jest-mock": "^26.5.0" + } + }, + "@jest/fake-timers": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.5.0.tgz", + "integrity": "sha512-sQK6xUembaZ0qLnZpSjJJuJiKvyrjCJhaYjbmatFpj5+cM8h2D7YEkeEBC26BMzvF1O3tNM9OL7roqyBmom0KA==", + "dev": true, + "requires": { + "@jest/types": "^26.5.0", + "@sinonjs/fake-timers": "^6.0.1", + "@types/node": "*", + "jest-message-util": "^26.5.0", + "jest-mock": "^26.5.0", + "jest-util": "^26.5.0" + } + }, + "@jest/globals": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.5.0.tgz", + "integrity": "sha512-TCKx3XWR9h/yyhQbz0C1sXkK2e8WJOnkP40T9bewNpf2Ahr1UEyKXnCoQO0JCpXFkWGTXBNo1QAgTQ3+LhXfcA==", + "dev": true, + "requires": { + "@jest/environment": "^26.5.0", + "@jest/types": "^26.5.0", + "expect": "^26.5.0" + } + }, + "@jest/source-map": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.5.0.tgz", + "integrity": "sha512-jWAw9ZwYHJMe9eZq/WrsHlwF8E3hM9gynlcDpOyCb9bR8wEd9ZNBZCi7/jZyzHxC7t3thZ10gO2IDhu0bPKS5g==", + "dev": true, + "requires": { + "callsites": "^3.0.0", + "graceful-fs": "^4.2.4", + "source-map": "^0.6.0" + } + }, + "@jest/test-result": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.5.0.tgz", + "integrity": "sha512-CaVXxDQi31LPOsz5/+iajNHQlA1Je/jQ8uYH/lCa6Y/UrkO+sDHeEH3x/inbx06PctVDnTwIlCcBvNNbC4FCvQ==", + "dev": true, + "requires": { + "@jest/console": "^26.5.0", + "@jest/types": "^26.5.0", + "@types/istanbul-lib-coverage": "^2.0.0", + "collect-v8-coverage": "^1.0.0" + } + }, + "@jest/test-sequencer": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.5.0.tgz", + "integrity": "sha512-23oofRXqPEy37HyHWIYf7lzzOqtGBkai5erZiL6RgxlyXE7a0lCihf6b5DfAvcD3yUtbXmh3EzpjJDVH57zQrg==", + "dev": true, + "requires": { + "@jest/test-result": "^26.5.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.5.0", + "jest-runner": "^26.5.0", + "jest-runtime": "^26.5.0" + } + }, + "@jest/transform": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.5.0.tgz", + "integrity": "sha512-Kt4WciOruTyTkJ2DZ+xtZiejRj3v22BrXCYZoGRbI0N6Q6tt2HdsWrrEtn6nlK24QWKC389xKkVk4Xr2gWBZQA==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/types": "^26.5.0", + "babel-plugin-istanbul": "^6.0.0", + "chalk": "^4.0.0", + "convert-source-map": "^1.4.0", + "fast-json-stable-stringify": "^2.0.0", + "graceful-fs": "^4.2.4", + "jest-haste-map": "^26.5.0", + "jest-regex-util": "^26.0.0", + "jest-util": "^26.5.0", + "micromatch": "^4.0.2", + "pirates": "^4.0.1", + "slash": "^3.0.0", + "source-map": "^0.6.1", + "write-file-atomic": "^3.0.0" + } + }, + "@jest/types": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.5.0.tgz", + "integrity": "sha512-nH9DFLqaIhB+RVgjivemvMiFSWw/BKwbZGxBAMv8CCTvUyFoK8RwHhAlmlXIvMBrf5Z3YQ4p9cq3Qh9EDctGvA==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^3.0.0", + "@types/node": "*", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0" + } + }, + "@types/stack-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", + "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", + "dev": true + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "babel-jest": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.5.0.tgz", + "integrity": "sha512-Cy16ZJrds81C+JASaOIGNlpCeqW3PTOq36owv+Zzwde5NiWz+zNduwxUNF57vxc/3SnIWo8HHqTczhN8GLoXTw==", + "dev": true, + "requires": { + "@jest/transform": "^26.5.0", + "@jest/types": "^26.5.0", + "@types/babel__core": "^7.1.7", + "babel-plugin-istanbul": "^6.0.0", + "babel-preset-jest": "^26.5.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "slash": "^3.0.0" + } + }, + "babel-plugin-jest-hoist": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.5.0.tgz", + "integrity": "sha512-ck17uZFD3CDfuwCLATWZxkkuGGFhMij8quP8CNhwj8ek1mqFgbFzRJ30xwC04LLscj/aKsVFfRST+b5PT7rSuw==", + "dev": true, + "requires": { + "@babel/template": "^7.3.3", + "@babel/types": "^7.3.3", + "@types/babel__core": "^7.0.0", + "@types/babel__traverse": "^7.0.6" + } + }, + "babel-preset-current-node-syntax": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.4.tgz", + "integrity": "sha512-5/INNCYhUGqw7VbVjT/hb3ucjgkVHKXY7lX3ZjlN4gm565VyFmJUrJ/h+h16ECVB38R/9SF6aACydpKMLZ/c9w==", + "dev": true, + "requires": { + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-bigint": "^7.8.3", + "@babel/plugin-syntax-class-properties": "^7.8.3", + "@babel/plugin-syntax-import-meta": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.8.3", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "babel-preset-jest": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.5.0.tgz", + "integrity": "sha512-F2vTluljhqkiGSJGBg/jOruA8vIIIL11YrxRcO7nviNTMbbofPSHwnm8mgP7d/wS7wRSexRoI6X1A6T74d4LQA==", + "dev": true, + "requires": { + "babel-plugin-jest-hoist": "^26.5.0", + "babel-preset-current-node-syntax": "^0.1.3" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "camelcase": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==", + "dev": true + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "cliui": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.1.tgz", + "integrity": "sha512-rcvHOWyGyid6I1WjT/3NatKj2kDt9OdSHSXpyLXaMWFbKpGACNW8pRhhdPUq9MWUOdwn8Rz9AVETjF4105rZZQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "diff-sequences": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.5.0.tgz", + "integrity": "sha512-ZXx86srb/iYy6jG71k++wBN9P9J05UNQ5hQHQd9MtMPvcqXPx/vKU69jfHV637D00Q2gSgPk2D+jSx3l1lDW/Q==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escalade": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.0.tgz", + "integrity": "sha512-mAk+hPSO8fLDkhV7V0dXazH5pDc6MrjBTPyD3VeKzxnVFjH1MIxbCdqGZB9O8+EwWakZs3ZCbDS4IpRt79V1ig==", + "dev": true + }, + "expect": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-26.5.0.tgz", + "integrity": "sha512-oIOy3mHWjnF5ZICuaui5kdtJZQ+D7XHWyUQDxk1WhIRCkcIYc24X23bOfikgCNU6i9wcSqLQhwPOqeRp09naxg==", + "dev": true, + "requires": { + "@jest/types": "^26.5.0", + "ansi-styles": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-matcher-utils": "^26.5.0", + "jest-message-util": "^26.5.0", + "jest-regex-util": "^26.0.0" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fsevents": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", + "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, + "optional": true + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "jest-config": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.5.0.tgz", + "integrity": "sha512-OM6eXIEmQXAuonCk8aNPMRjPFcKWa3IIoSlq5BPgIflmQBzM/COcI7XsWSIEPWPa9WcYTJBWj8kNqEYjczmIFw==", + "dev": true, + "requires": { + "@babel/core": "^7.1.0", + "@jest/test-sequencer": "^26.5.0", + "@jest/types": "^26.5.0", + "babel-jest": "^26.5.0", + "chalk": "^4.0.0", + "deepmerge": "^4.2.2", + "glob": "^7.1.1", + "graceful-fs": "^4.2.4", + "jest-environment-jsdom": "^26.5.0", + "jest-environment-node": "^26.5.0", + "jest-get-type": "^26.3.0", + "jest-jasmine2": "^26.5.0", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.5.0", + "jest-util": "^26.5.0", + "jest-validate": "^26.5.0", + "micromatch": "^4.0.2", + "pretty-format": "^26.5.0" + } + }, + "jest-diff": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.5.0.tgz", + "integrity": "sha512-CmDMMPkVMxrrh0Dv/4M9kh1tsYsZnYTQMMTvIFpePBSk9wMVfcyfg30TCq+oR9AzGbw8vsI50Gk1HmlMMlhoJg==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "diff-sequences": "^26.5.0", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.5.0" + } + }, + "jest-each": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.5.0.tgz", + "integrity": "sha512-+oO3ykDgypHSyyK2xOsh8XDUwMtg3HoJ4wMNFNHxhcACFbUgaCOfLy+eTCn5pIKhtigU3BmkYt7k3MtTb5pJOQ==", + "dev": true, + "requires": { + "@jest/types": "^26.5.0", + "chalk": "^4.0.0", + "jest-get-type": "^26.3.0", + "jest-util": "^26.5.0", + "pretty-format": "^26.5.0" + } + }, + "jest-environment-jsdom": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.5.0.tgz", + "integrity": "sha512-Xuqh3bx8egymaJR566ECkiztIIVOIWWPGIxo++ziWyCOqQChUguRCH1hRXBbfINPbb/SRFe7GCD+SunaUgTmCw==", + "dev": true, + "requires": { + "@jest/environment": "^26.5.0", + "@jest/fake-timers": "^26.5.0", + "@jest/types": "^26.5.0", + "@types/node": "*", + "jest-mock": "^26.5.0", + "jest-util": "^26.5.0", + "jsdom": "^16.4.0" + } + }, + "jest-environment-node": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.5.0.tgz", + "integrity": "sha512-LaYl/ek5mb1VDP1/+jMH2N1Ec4fFUhSYmc8EZqigBgMov/2US8U5l7D3IlOf78e+wARUxPxUpTcybVVzAOu3jg==", + "dev": true, + "requires": { + "@jest/environment": "^26.5.0", + "@jest/fake-timers": "^26.5.0", + "@jest/types": "^26.5.0", + "@types/node": "*", + "jest-mock": "^26.5.0", + "jest-util": "^26.5.0" + } + }, + "jest-haste-map": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.5.0.tgz", + "integrity": "sha512-AjB1b53uqN7Cf2VN80x0wJajVZ+BMZC+G2CmWoG143faaMw7IhIcs3FTPuSgOx7cn3/bag7lgCq93naAvLO6EQ==", + "dev": true, + "requires": { + "@jest/types": "^26.5.0", + "@types/graceful-fs": "^4.1.2", + "@types/node": "*", + "anymatch": "^3.0.3", + "fb-watchman": "^2.0.0", + "fsevents": "^2.1.2", + "graceful-fs": "^4.2.4", + "jest-regex-util": "^26.0.0", + "jest-serializer": "^26.5.0", + "jest-util": "^26.5.0", + "jest-worker": "^26.5.0", + "micromatch": "^4.0.2", + "sane": "^4.0.3", + "walker": "^1.0.7" + } + }, + "jest-jasmine2": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.5.0.tgz", + "integrity": "sha512-NOA6PLORHTRTROOp5VysKCUVpFAjMMXUS1Xw7FvTMeYK5Ewx4rpxhFqiJ7JT4pENap9g9OuXo4cWR/MwCDTEeQ==", + "dev": true, + "requires": { + "@babel/traverse": "^7.1.0", + "@jest/environment": "^26.5.0", + "@jest/source-map": "^26.5.0", + "@jest/test-result": "^26.5.0", + "@jest/types": "^26.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "co": "^4.6.0", + "expect": "^26.5.0", + "is-generator-fn": "^2.0.0", + "jest-each": "^26.5.0", + "jest-matcher-utils": "^26.5.0", + "jest-message-util": "^26.5.0", + "jest-runtime": "^26.5.0", + "jest-snapshot": "^26.5.0", + "jest-util": "^26.5.0", + "pretty-format": "^26.5.0", + "throat": "^5.0.0" + } + }, + "jest-leak-detector": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.5.0.tgz", + "integrity": "sha512-xZHvvTBbj3gUTtunLjPqP594BT6IUEpwA0AQpEQjVR8eBq8+R3qgU/KhoAcVcV0iqRM6pXtX7hKPZ5mLdynVSQ==", + "dev": true, + "requires": { + "jest-get-type": "^26.3.0", + "pretty-format": "^26.5.0" + } + }, + "jest-matcher-utils": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.5.0.tgz", + "integrity": "sha512-QgbbxqFT8wiTi4o/7MWj2vHlcmMjACG8vnJ9pJ7svVDmkzEnTUGdHXWLKB1aZhbnyXetMNRF+TSMcDS9aGfuzA==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "jest-diff": "^26.5.0", + "jest-get-type": "^26.3.0", + "pretty-format": "^26.5.0" + } + }, + "jest-message-util": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.5.0.tgz", + "integrity": "sha512-UEOqdoTfX0AFyReL4q5N3CfDBWt+AtQzeszZuuGapU39vwEk90rTSBghCA/3FFEZzvGfH2LE4+0NaBI81Cu2Ow==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "@jest/types": "^26.5.0", + "@types/stack-utils": "^2.0.0", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "micromatch": "^4.0.2", + "slash": "^3.0.0", + "stack-utils": "^2.0.2" + } + }, + "jest-mock": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.5.0.tgz", + "integrity": "sha512-8D1UmbnmjdkvTdYygTW26KZr95Aw0/3gEmMZQWkxIEAgEESVDbwDG8ygRlXSY214x9hFjtKezvfQUp36Ogl75w==", + "dev": true, + "requires": { + "@jest/types": "^26.5.0", + "@types/node": "*" + } + }, + "jest-resolve": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.5.0.tgz", + "integrity": "sha512-c34L8Lrw4fFzRiCLzwePziKRfHitjsAnY15ID0e9Se4ISikmZ5T9icLEFAGHnfaxfb+9r8EKdrbg89gjRdrQvw==", + "dev": true, + "requires": { + "@jest/types": "^26.5.0", + "chalk": "^4.0.0", + "escalade": "^3.1.0", + "graceful-fs": "^4.2.4", + "jest-pnp-resolver": "^1.2.2", + "jest-util": "^26.5.0", + "resolve": "^1.17.0", + "slash": "^3.0.0" + } + }, + "jest-runner": { + "version": "26.5.1", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.5.1.tgz", + "integrity": "sha512-gFHXehvMZD8qwNzaIl2MDFFI99m4kKk06H2xh2u4IkC+tHYIJjE5J175l9cbL3RuU2slfS2m57KZgcPZfbTavQ==", + "dev": true, + "requires": { + "@jest/console": "^26.5.0", + "@jest/environment": "^26.5.0", + "@jest/test-result": "^26.5.0", + "@jest/types": "^26.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "emittery": "^0.7.1", + "exit": "^0.1.2", + "graceful-fs": "^4.2.4", + "jest-config": "^26.5.0", + "jest-docblock": "^26.0.0", + "jest-haste-map": "^26.5.0", + "jest-leak-detector": "^26.5.0", + "jest-message-util": "^26.5.0", + "jest-resolve": "^26.5.0", + "jest-runtime": "^26.5.0", + "jest-util": "^26.5.0", + "jest-worker": "^26.5.0", + "source-map-support": "^0.5.6", + "throat": "^5.0.0" + } + }, + "jest-runtime": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.5.0.tgz", + "integrity": "sha512-CujjQWpMcsvSg0L+G3iEz6s7Th5IbiZseAaw/5R7Eb+IfnJdyPdjJ+EoXNV8n07snvW5nZTwV9QIfy6Vjris8A==", + "dev": true, + "requires": { + "@jest/console": "^26.5.0", + "@jest/environment": "^26.5.0", + "@jest/fake-timers": "^26.5.0", + "@jest/globals": "^26.5.0", + "@jest/source-map": "^26.5.0", + "@jest/test-result": "^26.5.0", + "@jest/transform": "^26.5.0", + "@jest/types": "^26.5.0", + "@types/yargs": "^15.0.0", + "chalk": "^4.0.0", + "collect-v8-coverage": "^1.0.0", + "exit": "^0.1.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.4", + "jest-config": "^26.5.0", + "jest-haste-map": "^26.5.0", + "jest-message-util": "^26.5.0", + "jest-mock": "^26.5.0", + "jest-regex-util": "^26.0.0", + "jest-resolve": "^26.5.0", + "jest-snapshot": "^26.5.0", + "jest-util": "^26.5.0", + "jest-validate": "^26.5.0", + "slash": "^3.0.0", + "strip-bom": "^4.0.0", + "yargs": "^16.0.3" + } + }, + "jest-serializer": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.5.0.tgz", + "integrity": "sha512-+h3Gf5CDRlSLdgTv7y0vPIAoLgX/SI7T4v6hy+TEXMgYbv+ztzbg5PSN6mUXAT/hXYHvZRWm+MaObVfqkhCGxA==", + "dev": true, + "requires": { + "@types/node": "*", + "graceful-fs": "^4.2.4" + } + }, + "jest-snapshot": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.5.0.tgz", + "integrity": "sha512-WTNJef67o7cCvwAe5foVCNqG3MzIW/CyU4FZvMrhBPZsJeXwfBY7kfOlydZigxtcytnvmNE2pqznOfD5EcQgrQ==", + "dev": true, + "requires": { + "@babel/types": "^7.0.0", + "@jest/types": "^26.5.0", + "@types/babel__traverse": "^7.0.4", + "@types/prettier": "^2.0.0", + "chalk": "^4.0.0", + "expect": "^26.5.0", + "graceful-fs": "^4.2.4", + "jest-diff": "^26.5.0", + "jest-get-type": "^26.3.0", + "jest-haste-map": "^26.5.0", + "jest-matcher-utils": "^26.5.0", + "jest-message-util": "^26.5.0", + "jest-resolve": "^26.5.0", + "natural-compare": "^1.4.0", + "pretty-format": "^26.5.0", + "semver": "^7.3.2" + } + }, + "jest-util": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.5.0.tgz", + "integrity": "sha512-CSQ0uzE7JdHDCQo3K8jlyWRIF2xNLdpu9nbjo8okGDanaNsF7WonhusFvjOg7QiWn1SThe7wFRh8Jx2ls1Gx4Q==", + "dev": true, + "requires": { + "@jest/types": "^26.5.0", + "@types/node": "*", + "chalk": "^4.0.0", + "graceful-fs": "^4.2.4", + "is-ci": "^2.0.0", + "micromatch": "^4.0.2" + } + }, + "jest-validate": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.5.0.tgz", + "integrity": "sha512-603+CHUJD4nAZ+tY/A+wu3g8KEcBey2a7YOMU9W8e4u7mCezhaDasw20ITaZHoR2R2MZhThL6jApPSj0GvezrQ==", + "dev": true, + "requires": { + "@jest/types": "^26.5.0", + "camelcase": "^6.0.0", + "chalk": "^4.0.0", + "jest-get-type": "^26.3.0", + "leven": "^3.1.0", + "pretty-format": "^26.5.0" + } + }, + "jest-worker": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.5.0.tgz", + "integrity": "sha512-kTw66Dn4ZX7WpjZ7T/SUDgRhapFRKWmisVAF0Rv4Fu8SLFD7eLbqpLvbxVqYhSgaWa7I+bW7pHnbyfNsH6stug==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^7.0.0" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "pretty-format": { + "version": "26.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.5.0.tgz", + "integrity": "sha512-NcgRuuTutUJ9+Br4P19DFThpJYnYBiugfRmZEA6pXrUeG+IcMSmppb88rU+iPA+XAJcjTYlCb5Ed6miHg/Qqqw==", + "dev": true, + "requires": { + "@jest/types": "^26.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + }, + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "string-width": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "strip-bom": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", + "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" } }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "y18n": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.2.tgz", + "integrity": "sha512-CkwaeZw6dQgqgPGeTWKMXCRmMcBgETFlTml1+ZOO+q7kGst8NREJ+eWwFNPVUQ4QGdAaklbqCZHH6Zuep1RjiA==", "dev": true }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "yargs": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.0.3.tgz", + "integrity": "sha512-6+nLw8xa9uK1BOEOykaiYAJVh6/CjxWXK/q9b5FpRgNslt8s22F2xMBqVIKgCRjNgGvGPBy8Vog7WN7yh4amtA==", "dev": true, "requires": { - "path-key": "^3.0.0" + "cliui": "^7.0.0", + "escalade": "^3.0.2", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.1", + "yargs-parser": "^20.0.0" } + }, + "yargs-parser": { + "version": "20.2.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.1.tgz", + "integrity": "sha512-yYsjuSkjbLMBp16eaOt7/siKTjNVjMm3SoJnIg3sEh/JsvqVVDyjRKmaJV4cl+lNIgq6QEco2i3gDebJl7/vLA==", + "dev": true } } }, @@ -9349,6 +10341,23 @@ "requires": { "errno": "^0.1.3", "readable-stream": "^2.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } }, "merge-descriptors": { @@ -10072,6 +11081,21 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } } } }, @@ -10502,8 +11526,7 @@ "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-limit": { "version": "3.0.2", @@ -10558,6 +11581,25 @@ } } }, + "p-queue": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/p-queue/-/p-queue-6.6.1.tgz", + "integrity": "sha512-miQiSxLYPYBxGkrldecZC18OTLjdUqnlRebGzPRiVxB8mco7usCmm7hFuxiTvp93K18JnLtE4KMMycjAu/cQQg==", + "requires": { + "eventemitter3": "^4.0.4", + "p-timeout": "^3.1.0" + }, + "dependencies": { + "p-timeout": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-3.2.0.tgz", + "integrity": "sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==", + "requires": { + "p-finally": "^1.0.0" + } + } + } + }, "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", @@ -10639,6 +11681,23 @@ "cyclist": "^1.0.1", "inherits": "^2.0.3", "readable-stream": "^2.1.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } }, "parent-module": { @@ -11160,6 +12219,23 @@ "requires": { "pinkie-promise": "^2.0.0", "readable-stream": "^2.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } }, "read-pkg": { @@ -11252,18 +12328,13 @@ } }, "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, "readdirp": { @@ -11276,6 +12347,341 @@ "graceful-fs": "^4.1.11", "micromatch": "^3.1.10", "readable-stream": "^2.0.2" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true, + "optional": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true, + "optional": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "optional": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "optional": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "optional": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "optional": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "optional": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "optional": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "optional": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "optional": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true, + "optional": true + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true, + "optional": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true, + "optional": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } }, "regenerate": { @@ -11631,8 +13037,7 @@ "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safe-regex": { "version": "1.1.0", @@ -12311,6 +13716,23 @@ "requires": { "inherits": "~2.0.1", "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } }, "stream-each": { @@ -12334,6 +13756,23 @@ "readable-stream": "^2.3.6", "to-arraybuffer": "^1.0.0", "xtend": "^4.0.0" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } }, "stream-shift": { @@ -12444,7 +13883,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -12697,6 +14135,23 @@ "requires": { "readable-stream": "~2.3.6", "xtend": "~4.0.1" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + } } }, "thunkify": { @@ -13100,8 +14555,7 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "utils-merge": { "version": "1.0.1", diff --git a/package.json b/package.json index 41ff2db84..9987c662a 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "express": "^4.17.1", "geckodriver": "^1.20.0", "jest": "^26.4.2", + "jest-circus": "^26.5.1", "nightwatch": "^1.4.1", "sinon": "^9.0.3", "streamr-test-utils": "^1.0.4", @@ -78,10 +79,12 @@ "once": "^1.4.0", "p-limit": "^3.0.2", "p-memoize": "^4.0.0", + "p-queue": "^6.6.1", "promise-memoize": "^1.2.1", "qs": "^6.9.4", "quick-lru": "^5.1.1", "randomstring": "^1.1.5", + "readable-stream": "^3.6.0", "streamr-client-protocol": "^5.3.3", "uuid": "^8.3.0", "webpack-node-externals": "^2.5.2", diff --git a/src/Stream.js b/src/Stream.js index 46339c66b..8b7608131 100644 --- a/src/Stream.js +++ b/src/Stream.js @@ -1,30 +1,26 @@ -import { PassThrough, finished } from 'stream' -import { promisify } from 'util' +import { PassThrough } from 'stream' import pMemoize from 'p-memoize' import pLimit from 'p-limit' -import { ControlLayer, MessageLayer } from 'streamr-client-protocol' +import { ControlLayer, MessageLayer, Utils, Errors } from 'streamr-client-protocol' import AbortController from 'node-abort-controller' -import { uuid } from './utils' +import SignatureRequiredError from './errors/SignatureRequiredError' +import { uuid, CacheAsyncFn, pOrderedResolve } from './utils' +import { endStream, pipeline, AbortError, CancelableGenerator } from './iterators' -const pFinished = promisify(finished) +const { OrderingUtil, StreamMessageValidator } = Utils + +const { ValidationError } = Errors const { SubscribeRequest, UnsubscribeRequest, ControlMessage, ResendLastRequest, ResendFromRequest, ResendRangeRequest, } = ControlLayer -const { MessageRef } = MessageLayer +const { MessageRef, StreamMessage } = MessageLayer -export class AbortError extends Error { - constructor(msg = '', ...args) { - super(`The operation was aborted. ${msg}`, ...args) - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor) - } - } -} +export { AbortError } /** * Convert allSettled results into a thrown Aggregate error if necessary. @@ -43,90 +39,6 @@ async function allSettledValues(items, errorMessage) { return result.map(({ value }) => value) } -/** - * Allows injecting a function to execute after an iterator finishes. - * Executes finally function even if generator not started. - */ - -function iteratorFinally(iterator, onFinally = () => {}) { - let started = false - const onFinallyOnce = pMemoize(onFinally) - const g = (async function* It() { - started = true - try { - yield* iterator - } finally { - await onFinallyOnce() - } - }()) - - // overrides return/throw to call onFinally even if generator was never started - const oldReturn = g.return - const oldThrow = g.throw - return Object.assign(g, { - return: async (...args) => { - if (!started) { - await onFinallyOnce(iterator) - } - return oldReturn.call(g, ...args) - }, - throw: async (...args) => { - if (!started) { - await onFinallyOnce() - } - return oldThrow.call(g, ...args) - }, - }) -} - -/** - * Iterates over a Stream - * Cleans up stream/stops iterator if either stream or iterator ends. - * Adds abort + end methods to iterator - */ - -function streamIterator(stream, { abortController, onFinally = () => {}, }) { - const onFinallyOnce = pMemoize(onFinally) // called once when stream ends - const endStreamOnce = pMemoize(async (optionalErr) => { - // ends stream + waits for end - stream.destroy(optionalErr) - await pFinished(stream, { - // necessary or can get premature close errors - // TODO: figure out why - readable: false, - writable: false, - }) - }) - - const it = iteratorFinally((async function* streamIteratorFn() { - yield* stream - }()), async () => { - await endStreamOnce() - await onFinallyOnce() - }) - - return Object.assign(it, { - stream, - async abort() { - if (abortController) { - abortController.abort() - } else { - await it.end(new AbortError()) - } - }, - async end(optionalErr) { - await endStreamOnce(optionalErr) - - if (optionalErr) { - await it.throw(optionalErr) - return - } - - await it.return() - } - }) -} - function getIsMatchingStreamMessage({ streamId, streamPartition = 0 }) { return function isMatchingStreamMessage({ streamMessage }) { const msgStreamId = streamMessage.getStreamId() @@ -142,26 +54,12 @@ function getIsMatchingStreamMessage({ streamId, streamPartition = 0 }) { * Write messages into a Stream. */ -function messageStream(connection, { streamId, streamPartition, signal, type = ControlMessage.TYPES.BroadcastMessage }) { - if (signal && signal.aborted) { - throw new AbortError() - } - +function messageStream(connection, { streamId, streamPartition, type = ControlMessage.TYPES.BroadcastMessage }) { // stream acts as buffer const msgStream = new PassThrough({ objectMode: true, }) - const onAbort = () => { - return msgStream.destroy(new AbortError()) - } - - if (signal) { - signal.addEventListener('abort', onAbort, { - once: true - }) - } - const isMatchingStreamMessage = getIsMatchingStreamMessage({ streamId, streamPartition @@ -178,17 +76,10 @@ function messageStream(connection, { streamId, streamPartition, signal, type = C // remove onMessage handler & clean up as soon as we see any 'end' events const onClose = () => { connection.off(type, onMessage) - // clean up abort signal - if (signal) { - signal.removeEventListener('abort', onAbort, { - once: true, - }) - } // clean up other handlers msgStream .off('close', onClose) .off('end', onClose) - .off('destroy', onClose) .off('finish', onClose) } @@ -283,6 +174,153 @@ async function waitForRequestResponse(client, request) { }) } +function OrderMessages(client, options = {}) { + const { gapFillTimeout, retryResendAfter } = client.options + const { streamId, streamPartition } = validateOptions(options) + + const outStream = new PassThrough({ + objectMode: true, + }) + + let done = false + + const orderingUtil = new OrderingUtil(streamId, streamPartition, (orderedMessage) => { + if (!outStream.writable || done) { + return + } + + if (orderedMessage.isByeMessage()) { + outStream.end(orderedMessage) + } else { + outStream.write(orderedMessage) + } + }, (from, to, publisherId, msgChainId) => async () => { + // eslint-disable-next-line no-use-before-define + const resendIt = await getResendStream(client, { + streamId, streamPartition, from, to, publisherId, msgChainId, + }) + if (done) { + return + } + for (const { streamMessage } of resendIt) { + orderingUtil.add(streamMessage) + } + }, gapFillTimeout, retryResendAfter) + + const markMessageExplicitly = orderingUtil.markMessageExplicitly.bind(orderingUtil) + + return Object.assign(pipeline([ + // eslint-disable-next-line require-yield + async function* WriteToOrderingUtil(src) { + for await (const msg of src) { + orderingUtil.add(msg) + } + }, + outStream, + async function* WriteToOrderingUtil(src) { + for await (const msg of src) { + yield msg + } + }, + ], async (err) => { + done = true + orderingUtil.clearGaps() + await endStream(outStream, err) + orderingUtil.clearGaps() + }), { + markMessageExplicitly, + }) +} + +function Validator(client, opts) { + const options = validateOptions(opts) + const cacheOptions = client.options.cache + const getStream = CacheAsyncFn(client.getStream.bind(client), cacheOptions) + const isStreamPublisher = CacheAsyncFn(client.isStreamPublisher.bind(client), cacheOptions) + const isStreamSubscriber = CacheAsyncFn(client.isStreamSubscriber.bind(client), cacheOptions) + + const validator = new StreamMessageValidator({ + getStream, + isPublisher: CacheAsyncFn(async (publisherId, _streamId) => ( + isStreamPublisher(_streamId, publisherId) + ), cacheOptions), + isSubscriber: CacheAsyncFn(async (ethAddress, _streamId) => ( + isStreamSubscriber(_streamId, ethAddress) + ), cacheOptions) + }) + + // return validation function that resolves in call order + return pOrderedResolve(async (msg) => { + // Check special cases controlled by the verifySignatures policy + if (client.options.verifySignatures === 'never' && msg.messageType === StreamMessage.MESSAGE_TYPES.MESSAGE) { + return msg // no validation required + } + + if (options.verifySignatures === 'always' && !msg.signature) { + throw new SignatureRequiredError(msg) + } + + // In all other cases validate using the validator + await validator.validate(msg) // will throw with appropriate validation failure + return msg + }) +} + +function MessagePipeline(client, opts = {}, onFinally = () => {}) { + const options = validateOptions(opts) + const { signal, validate = Validator(client, options) } = options + const stream = messageStream(client.connection, options) + const orderingUtil = OrderMessages(client, options) + let p + const onAbort = () => { + p.cancel(new AbortError()) + } + + signal.addEventListener('abort', onAbort, { + once: true + }) + + if (signal.aborted) { + stream.destroy(new AbortError()) + } + + p = pipeline([ + stream, + async function* Validate(src) { + for await (const { streamMessage } of src) { + try { + yield await validate(streamMessage) + } catch (err) { + if (err instanceof ValidationError) { + orderingUtil.markMessageExplicitly(streamMessage) + // eslint-disable-next-line no-continue + continue + } + } + } + }, + orderingUtil, + ], async (err) => { + signal.removeEventListener('abort', onAbort, { + once: true, + }) + try { + await endStream(stream, err) + } finally { + await onFinally(err) + } + }) + + return Object.assign(p, { + stream, + done: () => { + if (stream.writable) { + stream.end() + } + } + }) +} + // // Subscribe/Unsubscribe // @@ -379,17 +417,14 @@ async function resend(client, options) { return onResponse } -async function iterateResend(client, opts) { +async function getResendStream(client, opts) { const options = validateOptions(opts) const abortController = new AbortController() - const stream = messageStream(client.connection, { - signal: abortController.signal, - type: ControlMessage.TYPES.UnicastMessage, - ...options, - }) - const streamIt = streamIterator(stream, { - abortController, + const msgs = MessagePipeline(client, { + ...options, + type: ControlMessage.TYPES.UnicastMessage, + signal: abortController.signal, }) const requestId = uuid('rs') @@ -403,12 +438,10 @@ async function iterateResend(client, opts) { ControlMessage.TYPES.ResendResponseNoResend, ], }).then((v) => { - if (stream.writable) { - stream.end() - } + msgs.done() return v }, (err) => { - return streamIt.end(err) + return msgs.cancel(err) }) // wait for resend complete message or resend request done @@ -419,7 +452,7 @@ async function iterateResend(client, opts) { onResendDone ]) - return streamIt + return msgs } /** @@ -442,6 +475,7 @@ class Subscription { this.return = this.return.bind(this) this.sendSubscribe = pMemoize(this.sendSubscribe.bind(this)) this.sendUnsubscribe = pMemoize(this.sendUnsubscribe.bind(this)) + this.validate = Validator(client, options) } hasPending() { @@ -449,8 +483,7 @@ class Subscription { } async abort() { - this.abortController.abort() - await this.queue(() => {}) // pending tasks done + await this.abortController.abort() } async sendSubscribe() { @@ -468,15 +501,18 @@ class Subscription { return iterator } - async unsubscribe() { + async _unsubscribe() { pMemoize.clear(this.sendSubscribe) await this.sendUnsubscribe() } - async end(optionalErr) { - await allSettledValues([...this.streams].map(async (it) => { - await it.end(optionalErr) - }), 'end failed') + async cancel(optionalErr) { + if (this.hasPending()) { + await this.queue(() => {}) + } + await allSettledValues([...this.streams].map(async (it) => ( + it.cancel(optionalErr) + )), 'cancel failed') } async return() { @@ -485,13 +521,17 @@ class Subscription { }), 'return failed') } + async unsubscribe(...args) { + return this.cancel(...args) + } + async _cleanup(it) { // if iterator never started, finally block never called, thus need to manually clean it const hadStream = this.streams.has(it) this.streams.delete(it) if (hadStream && !this.streams.size) { // unsubscribe if no more streams - await this.unsubscribe() + await this._unsubscribe() } } @@ -500,22 +540,18 @@ class Subscription { } iterate() { - const stream = messageStream(this.client.connection, { - ...this.options, + const msgs = MessagePipeline(this.client, { signal: this.abortController.signal, + validate: this.validate, type: ControlMessage.TYPES.BroadcastMessage, + ...this.options, + }, async () => { + await this._cleanup(msgs) }) - const streamIt = streamIterator(stream, { - abortController: this.abortController, - onFinally: async () => { - await this._cleanup(streamIt) - } - }) - - this.streams.add(streamIt) + this.streams.add(msgs) - return streamIt + return msgs } [Symbol.asyncIterator]() { @@ -556,14 +592,11 @@ export default class Subscriptions { async unsubscribe(options) { const key = SubKey(validateOptions(options)) const sub = this.subscriptions.get(key) - if (!sub) { return } - - // wait for any outstanding operations - if (sub.hasPending()) { - await sub.queue(() => {}) + if (!sub) { + return } - await sub.return() // close all streams (thus unsubscribe) + await sub.cancel() // close all streams (thus unsubscribe) } async subscribe(options) { @@ -577,7 +610,7 @@ export default class Subscriptions { } async resend(opts) { - return iterateResend(this.client, opts) + return getResendStream(this.client, opts) } async resendSubscribe(options) { @@ -588,18 +621,20 @@ export default class Subscriptions { // end both on end async function end(optionalErr) { - await allSettledValues([ - sub.end(optionalErr), - resendSub.end(optionalErr), - ], 'resend end failed') + await Promise.all([ + sub.cancel(optionalErr), + resendSub.cancel(optionalErr), + ]) } - const it = iteratorFinally((async function* ResendSubIterator() { + const [, it] = CancelableGenerator((async function* ResendSubIterator() { // iterate over resend yield* resendSub // then iterate over realtime subscription yield* sub - }()), () => end()) + }()), async (err) => { + await end(err) + }) // attach additional utility functions return Object.assign(it, { @@ -608,13 +643,6 @@ export default class Subscriptions { abort: () => ( this.abortController && this.abortController.abort() ), - async end(optionalErr) { // eslint-disable-line require-atomic-updates - try { - await end(optionalErr) - } finally { - await it.return() - } - } }) } } diff --git a/src/Subscriber.js b/src/Subscriber.js index 08183db2f..27473ef00 100644 --- a/src/Subscriber.js +++ b/src/Subscriber.js @@ -110,17 +110,8 @@ export default class Subscriber { this.debug('WARN: InvalidJsonError received for stream with no subscriptions: %s', err.streamId) } } - async subscribe(...args) { - const sub = this.createSubscription(...args) - await Promise.all([ - sub.waitForSubscribed(), - this._resendAndSubscribe(sub), - ]) - return sub - } - - createSubscription(optionsOrStreamId, callback, legacyOptions) { + subscribe(optionsOrStreamId, callback, legacyOptions) { const options = this._validateParameters(optionsOrStreamId, callback) // Backwards compatibility for giving an options object as third argument @@ -185,25 +176,17 @@ export default class Subscriber { // Add to lookups this._addSubscription(sub) - return sub - } - - async unsubscribe(sub) { - if (!sub || !sub.streamId) { - throw new Error('unsubscribe: please give a Subscription object as an argument!') + // If connected, emit a subscribe request + if (this.client.isConnected()) { + this._resendAndSubscribe(sub).catch(this.onErrorEmit) + } else if (this.client.options.autoConnect) { + this.client.ensureConnected() } - if (sub.getState() === Subscription.State.unsubscribed) { - return Promise.resolve() - } - - return Promise.all([ - new Promise((resolve) => sub.once('unsubscribed', resolve)), - this._sendUnsubscribe(sub) - ]).then(() => Promise.resolve()) + return sub } - async _sendUnsubscribe(sub) { + async unsubscribe(sub) { if (!sub || !sub.streamId) { throw new Error('unsubscribe: please give a Subscription object as an argument!') } @@ -340,16 +323,16 @@ export default class Subscriber { if (subscribedSubs.length) { // If there already is a subscribed subscription for this stream, this new one will just join it immediately this.debug('_requestSubscribe: another subscription for same stream: %s, insta-subscribing', sub.streamId) - await true // wait a tick - sub.setState(Subscription.State.subscribed) + + setTimeout(() => { + sub.setState(Subscription.State.subscribed) + }) return } } const sessionToken = await this.client.session.getSessionToken() - // this should come after an async call e.g. getSessionToken - // so only one parallel call will send the subscription request if (sp.isSubscribing()) { return } @@ -361,12 +344,11 @@ export default class Subscriber { sessionToken, requestId: this.client.resender.resendUtil.generateRequestId(), }) - sp.setSubscribing(true) this.debug('_requestSubscribe: subscribing client: %o', request) await this.client.send(request).catch((err) => { sub.setState(Subscription.State.unsubscribed) - const error = new Error(`Failed to send subscribe request: ${err.stack}`) + const error = new Error(`Failed to sendnsubscribe request: ${err.stack}`) this.onErrorEmit(error) throw error }) @@ -381,8 +363,9 @@ export default class Subscriber { }) await this.client.connection.send(unsubscribeRequest).catch((err) => { sub.setState(Subscription.State.subscribed) - this.client.emit(new Error(`Failed to send unsubscribe request: ${err.stack}`)) - throw err + const error = new Error(`Failed to send unsubscribe request: ${err.stack}`) + this.onErrorEmit(error) + throw error }) return this._checkAutoDisconnect() } diff --git a/src/iterators.js b/src/iterators.js index 5c9d7eef4..653b2c99f 100644 --- a/src/iterators.js +++ b/src/iterators.js @@ -15,14 +15,14 @@ class TimeoutError extends Error { } } -async function pTimeout(promise, timeout, message = '') { +export async function pTimeout(promise, timeout = 0, message = '') { let t return Promise.race([ promise, new Promise((resolve, reject) => { t = setTimeout(() => { reject(new TimeoutError(message, timeout)) - }) + }, timeout) }) ]).finally(() => { clearTimeout(t) @@ -176,7 +176,7 @@ export function iteratorFinally(iterable, onFinally) { * const [cancal, generator] = CancelableGenerator(iterable, onFinally) */ -export function CancelableGenerator(iterable, onFinally, { timeout = 1000 } = {}) { +export function CancelableGenerator(iterable, onFinally, { timeout = 250 } = {}) { let started = false let cancelled = false let finalCalled = false @@ -315,29 +315,41 @@ const isPipeline = Symbol('isPipeline') const getIsStream = (item) => typeof item.pipe === 'function' -export function pipeline(iterables = [], onFinally) { +export function pipeline(iterables = [], onFinally, opts) { const cancelFns = new Set() let cancelled = false let error const onCancelDone = Defer() - const cancelAll = async (err) => { - if (cancelled) { - await onCancelDone - return - } + let pipelineValue + const cancelAll = async (err) => { cancelled = true error = err try { // eslint-disable-next-line promise/no-promise-in-callback - await Promise.all([...cancelFns].map(async (cancel) => ( + await allSettledValues([...cancelFns].map(async (cancel) => ( cancel(err) - ))).then(onCancelDone.resolve, onCancelDone.reject) + ))) } finally { cancelFns.clear() } } + const cancel = async (err) => { + if (cancelled) { + await onCancelDone + return + } + await cancelAll(err) + if (error) { + // eslint-disable-next-line promise/no-promise-in-callback + pipelineValue.throw(error).catch(() => {}) // ignore err + } else { + pipelineValue.return() + } + await onCancelDone + } + let firstSrc const setFirstSource = (v) => { firstSrc = v @@ -352,10 +364,13 @@ export function pipeline(iterables = [], onFinally) { let nextIterable = typeof next === 'function' ? next(prev) : next if (nextIterable[isPipeline]) { - cancelFns.add(nextIterable.cancel) nextIterable.setFirstSource(prev) } + if (nextIterable.cancel) { + cancelFns.add(nextIterable.cancel) + } + if (nextIterable && getIsStream(nextIterable)) { stream = nextIterable } @@ -382,24 +397,31 @@ export function pipeline(iterables = [], onFinally) { if (stream) { await endStream(stream, err || error) } - }) + }, opts) cancelFns.add(cancelCurrent) return it }, undefined) - const pipelineValue = iteratorFinally(last, async () => { + pipelineValue = iteratorFinally(last, async () => { if (!cancelled) { await cancelAll(error) } cancelFns.clear() - await onFinally(error) + try { + await onFinally(error) + if (error) { + throw error + } + } finally { + onCancelDone.resolve() + } }) return Object.assign(pipelineValue, { [isPipeline]: true, setFirstSource, - cancel: cancelAll, + cancel, }) } diff --git a/src/rest/authFetch.js b/src/rest/authFetch.js index 07dea48ee..0a7b3bb71 100644 --- a/src/rest/authFetch.js +++ b/src/rest/authFetch.js @@ -1,8 +1,9 @@ -import fetch from 'node-fetch' -import Debug from 'debug' import http from 'http' import https from 'https' +import fetch from 'node-fetch' +import Debug from 'debug' + import AuthFetchError from '../errors/AuthFetchError' import { getVersionString } from '../utils' @@ -20,7 +21,6 @@ export function getAgent(protocol) { if (!getAgent.httpAgent) { getAgent.httpAgent = new http.Agent({ keepAlive: true, - keepAliveMsecs: 10000, }) } return getAgent.httpAgent @@ -29,7 +29,6 @@ export function getAgent(protocol) { if (!getAgent.httpsAgent) { getAgent.httpsAgent = new https.Agent({ keepAlive: true, - keepAliveMsecs: 10000, }) } return getAgent.httpsAgent diff --git a/src/utils.js b/src/utils.js index 0b47cbfc7..51ed4e402 100644 --- a/src/utils.js +++ b/src/utils.js @@ -2,6 +2,7 @@ import { v4 as uuidv4 } from 'uuid' import uniqueId from 'lodash.uniqueid' import LRU from 'quick-lru' import pMemoize from 'p-memoize' +import pQueue from 'p-queue' import pLimit from 'p-limit' import mem from 'mem' @@ -165,3 +166,22 @@ export function Defer(executor = () => {}) { wrap, }) } + +export function pOrderedResolve(fn) { + const queue = pLimit(1) + return async (...args) => { + const d = Defer() + const done = queue(() => d) + // eslint-disable-next-line promise/catch-or-return + await Promise.resolve(fn(...args)).then(d.resolve, d.reject) + return done + } +} + +export function pOrderedResolveLimit(limit = 6, fn) { + const queue = pLimit(limit) + const orderedFn = pOrderedResolve(fn) + return async (...args) => ( + queue(orderedFn(...args)) + ) +} diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index d7b6dfbb2..09e24e237 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -1,8 +1,11 @@ import { ControlLayer } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' +import Debug from 'debug' import { uid, fakePrivateKey } from '../utils' import StreamrClient from '../../src' +import { pTimeout } from '../../src/iterators' +import { Defer } from '../../src/utils' import Connection from '../../src/Connection' import MessageStream from '../../src/Stream' @@ -18,7 +21,7 @@ const Msg = (opts) => ({ async function collect(iterator, fn = () => {}) { const received = [] for await (const msg of iterator) { - received.push(msg) + received.push(msg.getParsedContent()) await fn({ msg, iterator, received, }) @@ -27,7 +30,9 @@ async function collect(iterator, fn = () => {}) { return received } -const TEST_REPEATS = 1 +const TEST_REPEATS = 3 + +console.log = Debug('Streamr:: CONSOLE ') describe('StreamrClient Stream', () => { let expectErrors = 0 // check no errors by default @@ -51,21 +56,33 @@ describe('StreamrClient Stream', () => { return c } - beforeEach(async () => { + beforeAll(async () => { if (client) { await client.disconnect() } - // eslint-disable-next-line require-atomic-updates - client = createClient() - await client.connect() - console.log = client.debug + }) + + beforeEach(async () => { expectErrors = 0 onError = jest.fn() - stream = await client.createStream({ - name: uid('stream') - }) }) + beforeAll(async () => { + // eslint-disable-next-line require-atomic-updates + client = createClient() + + await pTimeout(client.connect(), 4500, 'client.connect') + }) + + beforeAll(async () => { + stream = undefined + stream = await pTimeout(client.createStream({ + name: uid('stream') + }), 10000, 'createStream') + + await wait(250) // prevent timing issues + }, 11000) + afterEach(async () => { await wait() // ensure no unexpected errors @@ -75,11 +92,12 @@ describe('StreamrClient Stream', () => { } }) - afterEach(async () => { + afterAll(async () => { await wait() if (client) { client.debug('disconnecting after test') - await client.disconnect() + await pTimeout(client.disconnect(), 4500, 'client.disconnect') + client.debug('disconnected after test') } const openSockets = Connection.getOpen() @@ -123,7 +141,7 @@ describe('StreamrClient Stream', () => { return } } - expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(received).toEqual(published) expect(M.count(stream.id)).toBe(0) }) @@ -133,13 +151,11 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(1) const published = [] - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 8; i++) { const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) published.push(message) - // eslint-disable-next-line no-await-in-loop - await wait(100) } const received = [] @@ -149,7 +165,7 @@ describe('StreamrClient Stream', () => { return } } - expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(received).toEqual(published) expect(M.count(stream.id)).toBe(0) }) @@ -178,7 +194,7 @@ describe('StreamrClient Stream', () => { t = setTimeout(() => { expectedLength = received.length // should not see any more messages after end - sub.end() + sub.cancel() }) } } @@ -224,6 +240,39 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) }) + it('can kill stream with abort', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) + + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + + const received = [] + await expect(async () => { + for await (const m of sub) { + received.push(m) + // after first message schedule end + if (received.length === 1) { + await M.abort(stream.id) + } + } + }).rejects.toThrow('abort') + // gets some messages but not all + expect(received).toHaveLength(1) + expect(unsubscribeEvents).toHaveLength(1) + expect(M.count(stream.id)).toBe(0) + }) + it('can subscribe to stream multiple times, get updates then unsubscribe', async () => { const M = new MessageStream(client) const sub1 = await M.subscribe(stream.id) @@ -253,7 +302,7 @@ describe('StreamrClient Stream', () => { }) ]) - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(received1).toEqual(published) expect(received2).toEqual(received1) expect(M.count(stream.id)).toBe(0) }) @@ -287,7 +336,7 @@ describe('StreamrClient Stream', () => { }) ]) - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(received1).toEqual(published) expect(received2).toEqual(received1) expect(M.count(stream.id)).toBe(0) }) @@ -307,13 +356,13 @@ describe('StreamrClient Stream', () => { const received = [] for await (const m of sub) { - received.push(m) + received.push(m.getParsedContent()) if (received.length === 1) { - await sub.end() + await sub.cancel() } } - expect(received.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(received).toEqual(published.slice(0, 1)) expect(M.count(stream.id)).toBe(0) }) @@ -335,10 +384,40 @@ describe('StreamrClient Stream', () => { } const received = [] for await (const m of sub) { - received.push(m) + received.push(m.getParsedContent()) + if (received.length === 2) { + expect(unsubscribeEvents).toHaveLength(0) + await sub.return() + expect(unsubscribeEvents).toHaveLength(1) + expect(M.count(stream.id)).toBe(0) + } + } + expect(received).toHaveLength(2) + expect(M.count(stream.id)).toBe(0) + }) + + it('finishes unsubscribe before returning from cancel', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) + + const M = new MessageStream(client) + const sub = await M.subscribe(stream.id) + + const published = [] + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + } + const received = [] + for await (const m of sub) { + received.push(m.getParsedContent()) if (received.length === 2) { expect(unsubscribeEvents).toHaveLength(0) - await sub.end() + await sub.cancel() expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) } @@ -365,12 +444,12 @@ describe('StreamrClient Stream', () => { } const received = [] for await (const m of sub) { - received.push(m) + received.push(m.getParsedContent()) if (received.length === 2) { expect(unsubscribeEvents).toHaveLength(0) const tasks = [ sub.return(), - sub.end(), + sub.cancel(), ] await Promise.race(tasks) expect(unsubscribeEvents).toHaveLength(1) @@ -401,13 +480,13 @@ describe('StreamrClient Stream', () => { } const received = [] for await (const m of sub) { - received.push(m) + received.push(m.getParsedContent()) if (received.length === 2) { expect(unsubscribeEvents).toHaveLength(0) const tasks = [ - sub.end(), - sub.end(), - sub.end(), + sub.cancel(), + sub.cancel(), + sub.cancel(), ] await Promise.race(tasks) expect(unsubscribeEvents).toHaveLength(1) @@ -429,14 +508,18 @@ describe('StreamrClient Stream', () => { beforeEach(async () => { M = new MessageStream(client) - sub1 = await M.subscribe(stream.id) - sub2 = await M.subscribe(stream.id) + sub1 = await pTimeout(M.subscribe(stream.id), 1500, 'subscribe 1') + sub2 = await pTimeout(M.subscribe(stream.id), 1500, 'subscribe 2') + }) + beforeEach(async () => { published = [] for (let i = 0; i < 5; i++) { const message = Msg() // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) + await pTimeout(( + client.publish(stream.id, message) + ), 1500, `publish msg ${i}`) published.push(message) // eslint-disable-next-line no-await-in-loop await wait(100) @@ -444,36 +527,51 @@ describe('StreamrClient Stream', () => { }) it('can subscribe to stream multiple times then unsubscribe all mid-stream', async () => { + let sub1Received + let sub1ReceivedAtUnsubscribe + const gotOne = Defer() const [received1, received2] = await Promise.all([ - collect(sub1), + collect(sub1, async ({ received }) => { + sub1Received = received + gotOne.resolve() + }), collect(sub2, async ({ received }) => { + await gotOne if (received.length === 1) { + sub1ReceivedAtUnsubscribe = sub1Received.slice() await M.unsubscribe(stream.id) } }), ]) - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) - expect(received2.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(received2).toEqual(published.slice(0, 1)) + expect(received1).toEqual(published.slice(0, sub1ReceivedAtUnsubscribe.length)) + expect(sub1ReceivedAtUnsubscribe).toEqual(sub1Received) expect(M.count(stream.id)).toBe(0) }) it('can subscribe to stream multiple times then abort mid-stream', async () => { let received1 let received2 + let sub1ReceivedAtUnsubscribe + const gotOne = Defer() await Promise.all([ expect(() => collect(sub1, ({ received }) => { received1 = received + gotOne.resolve() })).rejects.toThrow('abort'), expect(() => collect(sub2, async ({ received }) => { + await gotOne received2 = received if (received.length === 1) { + sub1ReceivedAtUnsubscribe = received1.slice() await M.abort(stream.id) } })).rejects.toThrow('abort'), ]) - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) - expect(received2.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(received1).toEqual(sub1ReceivedAtUnsubscribe) + expect(received1).toEqual(published.slice(0, sub1ReceivedAtUnsubscribe.length)) + expect(received2).toEqual(published.slice(0, 1)) expect(M.count(stream.id)).toBe(0) }) @@ -492,8 +590,8 @@ describe('StreamrClient Stream', () => { }) ]) - expect(received1.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 4)) - expect(received2.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(0, 1)) + expect(received1).toEqual(published.slice(0, 4)) + expect(received2).toEqual(published.slice(0, 1)) expect(M.count(stream.id)).toBe(0) }) @@ -513,7 +611,7 @@ describe('StreamrClient Stream', () => { const received = [] for await (const m of sub) { - received.push(m) + received.push(m.getParsedContent()) } expect(received).toHaveLength(0) expect(unsubscribeEvents).toHaveLength(1) @@ -542,7 +640,7 @@ describe('StreamrClient Stream', () => { const received = [] for await (const m of sub) { - received.push(m) + received.push(m.getParsedContent()) } // shouldn't get any messages diff --git a/test/integration/StreamResends.test.js b/test/integration/StreamResends.test.js index f956e2b36..195011d5a 100644 --- a/test/integration/StreamResends.test.js +++ b/test/integration/StreamResends.test.js @@ -1,6 +1,5 @@ import { ControlLayer } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' -import Debug from 'debug' import { uid, fakePrivateKey } from '../utils' import StreamrClient from '../../src' @@ -20,7 +19,7 @@ const Msg = (opts) => ({ async function collect(iterator, fn = () => {}) { const received = [] for await (const msg of iterator) { - received.push(msg) + received.push(msg.getParsedContent()) await fn({ msg, iterator, received, }) @@ -29,12 +28,10 @@ async function collect(iterator, fn = () => {}) { return received } -const TEST_REPEATS = 1 +const TEST_REPEATS = 3 /* eslint-disable no-await-in-loop */ -console.log = Debug('Streamr:: CONSOLE ') - const WAIT_FOR_STORAGE_TIMEOUT = 6000 describe('resends', () => { @@ -43,7 +40,6 @@ describe('resends', () => { let client let stream let published - let emptyStream const createClient = (opts = {}) => { const c = new StreamrClient({ @@ -112,6 +108,10 @@ describe('resends', () => { name: uid('stream'), }) + await wait(2000) + }) + + beforeAll(async () => { published = [] await client.connect() for (let i = 0; i < 5; i++) { @@ -119,6 +119,7 @@ describe('resends', () => { // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) published.push(message) + await wait(50) } const lastMessage = published[published.length - 1] @@ -130,9 +131,6 @@ describe('resends', () => { }, WAIT_FOR_STORAGE_TIMEOUT * 2) beforeEach(async () => { - emptyStream = await client.createStream({ - name: uid('stream') - }) await client.connect() expectErrors = 0 onError = jest.fn() @@ -164,7 +162,14 @@ describe('resends', () => { // eslint-disable-next-line no-loop-func describe(`test repeat ${k + 1} of ${TEST_REPEATS}`, () => { describe('no data', () => { + let emptyStream + it('handles nothing to resend', async () => { + emptyStream = await client.createStream({ + name: uid('stream') + }) + await wait(3000) + const M = new MessageStream(client) const sub = await M.resend({ streamId: emptyStream.id, @@ -173,11 +178,15 @@ describe('resends', () => { const receivedMsgs = await collect(sub) expect(receivedMsgs).toHaveLength(0) - expect(M.count(stream.id)).toBe(0) + expect(M.count(emptyStream.id)).toBe(0) }) it('resendSubscribe with nothing to resend', async () => { const M = new MessageStream(client) + + emptyStream = await client.createStream({ + name: uid('stream') + }) const sub = await M.resendSubscribe({ streamId: emptyStream.id, last: 5, @@ -194,7 +203,6 @@ describe('resends', () => { wait(100) break } - expect(received).toHaveLength(1) expect(M.count(emptyStream.id)).toBe(0) }) @@ -217,10 +225,9 @@ describe('resends', () => { streamId: stream.id, last: published.length, }) - const receivedMsgs = await collect(sub) expect(receivedMsgs).toHaveLength(published.length) - expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published) + expect(receivedMsgs).toEqual(published) expect(M.count(stream.id)).toBe(0) }) @@ -233,7 +240,7 @@ describe('resends', () => { const receivedMsgs = await collect(sub) expect(receivedMsgs).toHaveLength(2) - expect(receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent())).toEqual(published.slice(-2)) + expect(receivedMsgs).toEqual(published.slice(-2)) expect(M.count(stream.id)).toBe(0) }) @@ -273,7 +280,7 @@ describe('resends', () => { } }) - const msgs = receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent()) + const msgs = receivedMsgs expect(msgs).toHaveLength(published.length) expect(msgs).toEqual(published) expect(M.count(stream.id)).toBe(0) @@ -294,13 +301,14 @@ describe('resends', () => { // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) // should be realtime published.push(message) + await wait(500) const receivedMsgs = await collect(sub, async ({ received }) => { if (received.length === published.length) { await sub.return() } }) - const msgs = receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent()) + const msgs = receivedMsgs expect(msgs).toHaveLength(published.length) expect(msgs).toEqual(published) expect(M.count(stream.id)).toBe(0) @@ -324,6 +332,7 @@ describe('resends', () => { // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) published.push(message) + await wait(500) const received = [] for await (const m of sub) { received.push(m) @@ -346,6 +355,7 @@ describe('resends', () => { // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) published.push(message) + await wait(500) let t let receivedMsgs @@ -353,7 +363,7 @@ describe('resends', () => { receivedMsgs = await collect(sub, async ({ received }) => { if (received.length === published.length) { t = setTimeout(() => { - sub.end() + sub.cancel() }) } }) @@ -361,7 +371,7 @@ describe('resends', () => { clearTimeout(t) } - const msgs = receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent()) + const msgs = receivedMsgs expect(msgs).toHaveLength(published.length) expect(msgs).toEqual(published) expect(M.count(stream.id)).toBe(0) @@ -384,14 +394,15 @@ describe('resends', () => { // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) published.push(message) + await wait(500) const END_AFTER = 3 const receivedMsgs = await collect(sub, async ({ received }) => { if (received.length === END_AFTER) { - await sub.end() + await sub.cancel() expect(unsubscribeEvents).toHaveLength(1) } }) - const msgs = receivedMsgs.map(({ streamMessage }) => streamMessage.getParsedContent()) + const msgs = receivedMsgs expect(msgs).toHaveLength(END_AFTER) expect(msgs).toEqual(published.slice(0, END_AFTER)) expect(M.count(stream.id)).toBe(0) diff --git a/test/unit/iterators.test.js b/test/unit/iterators.test.js index c5d8735bb..44bd9165a 100644 --- a/test/unit/iterators.test.js +++ b/test/unit/iterators.test.js @@ -1,13 +1,10 @@ -import { Readable, PassThrough, finished } from 'stream' +import { Readable, PassThrough } from 'stream' -import Debug from 'debug' import { wait } from 'streamr-test-utils' import { iteratorFinally, CancelableGenerator, pipeline } from '../../src/iterators' import { Defer } from '../../src/utils' -console.log = Debug('Streamr:: CONSOLE ') - const expected = [1, 2, 3, 4, 5, 6, 7, 8] const WAIT = 20 @@ -294,12 +291,12 @@ describe('Iterator Utils', () => { }) describe('nesting', () => { - let onFinallyAfterInner + let onFinallyInnerAfter let onFinallyInner beforeEach(() => { - onFinallyAfterInner = jest.fn() - const afterInner = onFinallyAfterInner // capture so won't run afterInner of another test + onFinallyInnerAfter = jest.fn() + const afterInner = onFinallyInnerAfter // capture so won't run afterInner of another test onFinallyInner = jest.fn(async () => { await wait(WAIT) afterInner() @@ -308,7 +305,7 @@ describe('Iterator Utils', () => { afterEach(() => { expect(onFinallyInner).toHaveBeenCalledTimes(1) - expect(onFinallyAfterInner).toHaveBeenCalledTimes(1) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) }) IteratorTest('iteratorFinally nested', () => { @@ -466,15 +463,19 @@ describe('Iterator Utils', () => { it('cancels when iterator.cancel() is called asynchronously', async () => { const received = [] - const [cancel, itr] = CancelableGenerator(generate(), onFinally) + const [cancel, itr] = CancelableGenerator(generate(), onFinally, { + timeout: WAIT, + }) let receievedAtCallTime for await (const msg of itr) { received.push(msg) if (received.length === MAX_ITEMS) { // eslint-disable-next-line no-loop-func - setTimeout(() => { + setTimeout(async () => { receievedAtCallTime = received - cancel() + await cancel() + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) }) } } @@ -493,6 +494,8 @@ describe('Iterator Utils', () => { received.push(msg) if (received.length === expected.length) { await cancel() + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) } } @@ -504,14 +507,18 @@ describe('Iterator Utils', () => { const [cancel, itr] = CancelableGenerator((async function* Gen() { yield* expected yield await new Promise(() => {}) // would wait forever - }()), onFinally) + }()), onFinally, { + timeout: WAIT, + }) for await (const msg of itr) { received.push(msg) if (received.length === expected.length) { // eslint-disable-next-line no-loop-func - setTimeout(() => { - cancel() + setTimeout(async () => { + await cancel() + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) }) } } @@ -551,7 +558,9 @@ describe('Iterator Utils', () => { const [cancel, itr] = CancelableGenerator((async function* Gen() { yield* expected yield await new Promise(() => {}) // would wait forever - }()), onFinally) + }()), onFinally, { + timeout: WAIT, + }) const err = new Error('expected') @@ -561,9 +570,12 @@ describe('Iterator Utils', () => { received.push(msg) if (received.length === MAX_ITEMS) { // eslint-disable-next-line no-loop-func - setTimeout(() => { + setTimeout(async () => { receievedAtCallTime = received - cancel(err) + await cancel(err) + + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) }) } } @@ -576,13 +588,18 @@ describe('Iterator Utils', () => { const triggeredForever = jest.fn() const [cancel, itr] = CancelableGenerator((async function* Gen() { yield* expected - setTimeout(() => { - cancel() + setTimeout(async () => { + await cancel() + + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) }, WAIT * 2) yield await new Promise(() => { triggeredForever() }) // would wait forever - }()), onFinally) + }()), onFinally, { + timeout: WAIT, + }) const tasks = expected.map(async () => itr.next()) tasks.push(itr.next()) // one more over the edge (should trigger forever promise) @@ -601,14 +618,19 @@ describe('Iterator Utils', () => { yield v } - setTimeout(() => { - cancel() + setTimeout(async () => { + await cancel() + + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) }, WAIT * 2) yield await new Promise(() => { triggeredForever() }) // would wait forever - }()), onFinally) + }()), onFinally, { + timeout: WAIT, + }) const tasks = expected.map(async () => itr.next()) tasks.push(itr.next()) // one more over the edge (should trigger forever promise) @@ -634,9 +656,12 @@ describe('Iterator Utils', () => { received.push(msg) if (received.length === MAX_ITEMS) { // eslint-disable-next-line no-loop-func - setTimeout(() => { + setTimeout(async () => { receievedAtCallTime = received - cancel(err) + await cancel(err) + + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) }) } } @@ -649,12 +674,12 @@ describe('Iterator Utils', () => { }) describe('nesting', () => { - let onFinallyAfterInner + let onFinallyInnerAfter let onFinallyInner beforeEach(() => { - onFinallyAfterInner = jest.fn() - const afterInner = onFinallyAfterInner // capture so won't run afterInner of another test + onFinallyInnerAfter = jest.fn() + const afterInner = onFinallyInnerAfter // capture so won't run afterInner of another test onFinallyInner = jest.fn(async () => { await wait(WAIT) afterInner() @@ -663,7 +688,7 @@ describe('Iterator Utils', () => { afterEach(() => { expect(onFinallyInner).toHaveBeenCalledTimes(1) - expect(onFinallyAfterInner).toHaveBeenCalledTimes(1) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) }) IteratorTest('CancelableGenerator nested', () => { @@ -680,7 +705,9 @@ describe('Iterator Utils', () => { // should not get here waitInner() }) // would wait forever - }()), onFinallyInner) + }()), onFinallyInner, { + timeout: WAIT, + }) const waitOuter = jest.fn() const [cancelOuter, itrOuter] = CancelableGenerator((async function* Gen() { @@ -691,7 +718,11 @@ describe('Iterator Utils', () => { }) // would wait forever }()), async () => { await cancelInner() + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) await onFinally() + }, { + timeout: WAIT, }) const received = [] @@ -715,7 +746,9 @@ describe('Iterator Utils', () => { // should not get here waitInner() }) // would wait forever - }()), onFinallyInner) + }()), onFinallyInner, { + timeout: WAIT, + }) const waitOuter = jest.fn() const [cancelOuter, itrOuter] = CancelableGenerator((async function* Gen() { @@ -727,6 +760,8 @@ describe('Iterator Utils', () => { }()), async () => { await cancelInner() await onFinally() + }, { + timeout: WAIT, }) const received = [] @@ -1260,7 +1295,9 @@ describe('Iterator Utils', () => { } }, p1 - ], onFinally) + ], onFinally, { + timeout: WAIT, + }) const received = [] await expect(async () => { @@ -1307,7 +1344,9 @@ describe('Iterator Utils', () => { } }, throughStream2, - ], onFinally) + ], onFinally, { + timeout: WAIT, + }) const received = [] for await (const msg of p) { @@ -1348,7 +1387,9 @@ describe('Iterator Utils', () => { } }, throughStream2, - ], onFinally) + ], onFinally, { + timeout: WAIT, + }) const received = [] for await (const msg of p) { @@ -1384,7 +1425,9 @@ describe('Iterator Utils', () => { } } }, - ], onFinally) + ], onFinally, { + timeout: WAIT, + }) const received = [] await expect(async () => { @@ -1435,7 +1478,9 @@ describe('Iterator Utils', () => { } } }, - ], onFinally) + ], onFinally, { + timeout: WAIT, + }) const received = [] await expect(async () => { @@ -1469,7 +1514,9 @@ describe('Iterator Utils', () => { yield msg } }, - ], onFinallyInner) + ], onFinallyInner, { + timeout: WAIT, + }) const p = pipeline([ generate(), @@ -1480,7 +1527,9 @@ describe('Iterator Utils', () => { yield msg } }, - ], onFinally) + ], onFinally, { + timeout: WAIT, + }) const received = [] for await (const msg of p) { @@ -1518,7 +1567,9 @@ describe('Iterator Utils', () => { yield msg } }, - ], onFinallyInner) + ], onFinallyInner, { + timeout: WAIT, + }) const firstStream = Readable.from(generate()) firstStream.once('close', onFirstStreamClose) @@ -1531,7 +1582,9 @@ describe('Iterator Utils', () => { } }, p1, - ], onFinally) + ], onFinally, { + timeout: WAIT, + }) const received = [] for await (const msg of p) { @@ -1574,7 +1627,9 @@ describe('Iterator Utils', () => { } } }, - ], onFinallyInner) + ], onFinallyInner, { + timeout: WAIT, + }) const firstStream = Readable.from(generate()) firstStream.once('close', onFirstStreamClose) @@ -1587,14 +1642,142 @@ describe('Iterator Utils', () => { yield msg } }, - ], onFinally) + ], onFinally, { + timeout: WAIT, + }) + + const received = [] + for await (const msg of p) { + received.push(msg) + } + + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + expect(receivedStep1).toEqual(expected.slice(0, MAX_ITEMS)) + expect(receivedStep2).toEqual(expected.slice(0, MAX_ITEMS)) + // all streams were closed + expect(onFirstStreamClose).toHaveBeenCalledTimes(1) + expect(onInputStreamClose).toHaveBeenCalledTimes(1) + + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) + }) + + it('works with nested pipelines & streams + cancel before done', async () => { + const onFinallyInnerAfter = jest.fn() + const onFinallyInner = jest.fn(async () => { + await wait(WAIT) + onFinallyInnerAfter() + }) + const receivedStep1 = [] + const receivedStep2 = [] + const onFirstStreamClose = jest.fn() + const onInputStreamClose = jest.fn() + + const inputStream = new PassThrough({ + objectMode: true, + }) + inputStream.once('close', onInputStreamClose) + const p1 = pipeline([ + inputStream, + async function* Step1(s) { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg + if (receivedStep1.length === MAX_ITEMS) { + await p1.cancel() + } + } + }, + ], onFinallyInner, { + timeout: WAIT, + }) + + const firstStream = Readable.from(generate()) + firstStream.once('close', onFirstStreamClose) + const p = pipeline([ + firstStream, + p1, + async function* Step2(s) { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg + } + }, + ], onFinally, { + timeout: WAIT, + }) const received = [] for await (const msg of p) { received.push(msg) } - await wait(1000) + expect(received).toEqual(expected.slice(0, MAX_ITEMS)) + expect(receivedStep1).toEqual(expected.slice(0, MAX_ITEMS)) + expect(receivedStep2).toEqual(expected.slice(0, MAX_ITEMS)) + // all streams were closed + expect(onFirstStreamClose).toHaveBeenCalledTimes(1) + expect(onInputStreamClose).toHaveBeenCalledTimes(1) + + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) + }) + + it('works with nested pipelines & streams + cancel before done in second pipeline', async () => { + const onFinallyInnerAfter = jest.fn() + const onFinallyInner = jest.fn(async () => { + await wait(WAIT) + onFinallyInnerAfter() + }) + const receivedStep1 = [] + const receivedStep2 = [] + const onFirstStreamClose = jest.fn() + const onInputStreamClose = jest.fn() + + const inputStream = new PassThrough({ + objectMode: true, + }) + inputStream.once('close', onInputStreamClose) + + let p + const p1 = pipeline([ + inputStream, + async function* Step1(s) { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg + if (receivedStep1.length === MAX_ITEMS) { + await p.cancel() + expect(onFinally).toHaveBeenCalledTimes(1) + expect(onFinallyAfter).toHaveBeenCalledTimes(1) + expect(onFinallyInner).toHaveBeenCalledTimes(1) + expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) + } + } + }, + ], onFinallyInner, { + timeout: WAIT, + }) + + const firstStream = Readable.from(generate()) + firstStream.once('close', onFirstStreamClose) + p = pipeline([ + firstStream, + p1, + async function* Step2(s) { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg + } + }, + ], onFinally, { + timeout: WAIT, + }) + + const received = [] + for await (const msg of p) { + received.push(msg) + } expect(received).toEqual(expected.slice(0, MAX_ITEMS)) expect(receivedStep1).toEqual(expected.slice(0, MAX_ITEMS)) @@ -1631,7 +1814,9 @@ describe('Iterator Utils', () => { yield msg } }, - ], onFinallyInner) + ], onFinallyInner, { + timeout: WAIT, + }) const firstStream = Readable.from(generate()) firstStream.once('close', onFirstStreamClose) @@ -1644,7 +1829,9 @@ describe('Iterator Utils', () => { } }, p1, - ], onFinally) + ], onFinally, { + timeout: WAIT, + }) const received = [] for await (const msg of p) { diff --git a/webpack.config.js b/webpack.config.js index 02916da9b..eaa7971e4 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -75,6 +75,9 @@ const clientConfig = merge({}, commonConfig, { libraryTarget: 'umd2', filename: libraryName + '.web.js', }, + node: { + stream: 'readable-stream', + }, resolve: { alias: { http: path.resolve(__dirname, './src/shim/http-https.js'), From c22ca056ba8933f692356beeec6115b5ece1e7a1 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 13 Oct 2020 18:13:05 -0400 Subject: [PATCH 096/517] Reuse Subscriptions container in test. --- test/integration/Stream.test.js | 51 +++++++++++---------------------- 1 file changed, 16 insertions(+), 35 deletions(-) diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index 09e24e237..5a2079654 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -39,6 +39,7 @@ describe('StreamrClient Stream', () => { let onError = jest.fn() let client let stream + let M const createClient = (opts = {}) => { const c = new StreamrClient({ @@ -67,15 +68,14 @@ describe('StreamrClient Stream', () => { onError = jest.fn() }) - beforeAll(async () => { + beforeEach(async () => { // eslint-disable-next-line require-atomic-updates client = createClient() - + M = new MessageStream(client) await pTimeout(client.connect(), 4500, 'client.connect') }) - beforeAll(async () => { - stream = undefined + beforeEach(async () => { stream = await pTimeout(client.createStream({ name: uid('stream') }), 10000, 'createStream') @@ -92,7 +92,7 @@ describe('StreamrClient Stream', () => { } }) - afterAll(async () => { + afterEach(async () => { await wait() if (client) { client.debug('disconnecting after test') @@ -107,7 +107,6 @@ describe('StreamrClient Stream', () => { }) it('attaches listener at subscribe time', async () => { - const M = new MessageStream(client) const beforeCount = client.connection.listenerCount(ControlMessage.TYPES.BroadcastMessage) const sub = await M.subscribe(stream.id) const afterCount = client.connection.listenerCount(ControlMessage.TYPES.BroadcastMessage) @@ -122,12 +121,11 @@ describe('StreamrClient Stream', () => { describe(`test repeat ${k + 1} of ${TEST_REPEATS}`, () => { describe('basics', () => { it('can subscribe to stream and get updates then auto unsubscribe', async () => { - const M = new MessageStream(client) const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) const published = [] - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 3; i++) { const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) @@ -146,12 +144,11 @@ describe('StreamrClient Stream', () => { }) it('subscribes immediately', async () => { - const M = new MessageStream(client) const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) const published = [] - for (let i = 0; i < 8; i++) { + for (let i = 0; i < 3; i++) { const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) @@ -170,12 +167,11 @@ describe('StreamrClient Stream', () => { }) it('can kill stream using async end', async () => { - const M = new MessageStream(client) const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) const published = [] - for (let i = 0; i < 2; i++) { + for (let i = 0; i < 3; i++) { const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) @@ -211,12 +207,11 @@ describe('StreamrClient Stream', () => { client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { unsubscribeEvents.push(m) }) - const M = new MessageStream(client) const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) const published = [] - for (let i = 0; i < 2; i++) { + for (let i = 0; i < 3; i++) { const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) @@ -245,12 +240,11 @@ describe('StreamrClient Stream', () => { client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { unsubscribeEvents.push(m) }) - const M = new MessageStream(client) const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) const published = [] - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 3; i++) { const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) @@ -274,19 +268,17 @@ describe('StreamrClient Stream', () => { }) it('can subscribe to stream multiple times, get updates then unsubscribe', async () => { - const M = new MessageStream(client) const sub1 = await M.subscribe(stream.id) const sub2 = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(2) const published = [] - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 3; i++) { const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) published.push(message) // eslint-disable-next-line no-await-in-loop - await wait(100) } const [received1, received2] = await Promise.all([ @@ -308,7 +300,6 @@ describe('StreamrClient Stream', () => { }) it('can subscribe to stream multiple times in parallel, get updates then unsubscribe', async () => { - const M = new MessageStream(client) const [sub1, sub2] = await Promise.all([ M.subscribe(stream.id), M.subscribe(stream.id), @@ -316,7 +307,7 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(2) const published = [] - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 3; i++) { const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) @@ -342,7 +333,6 @@ describe('StreamrClient Stream', () => { }) it('can subscribe to stream and get some updates then unsubscribe mid-stream with end', async () => { - const M = new MessageStream(client) const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) @@ -372,11 +362,10 @@ describe('StreamrClient Stream', () => { unsubscribeEvents.push(m) }) - const M = new MessageStream(client) const sub = await M.subscribe(stream.id) const published = [] - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 3; i++) { const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) @@ -402,11 +391,10 @@ describe('StreamrClient Stream', () => { unsubscribeEvents.push(m) }) - const M = new MessageStream(client) const sub = await M.subscribe(stream.id) const published = [] - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 3; i++) { const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) @@ -432,11 +420,10 @@ describe('StreamrClient Stream', () => { unsubscribeEvents.push(m) }) - const M = new MessageStream(client) const sub = await M.subscribe(stream.id) const published = [] - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 3; i++) { const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) @@ -468,11 +455,10 @@ describe('StreamrClient Stream', () => { unsubscribeEvents.push(m) }) - const M = new MessageStream(client) const sub = await M.subscribe(stream.id) const published = [] - for (let i = 0; i < 5; i++) { + for (let i = 0; i < 3; i++) { const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) @@ -503,11 +489,9 @@ describe('StreamrClient Stream', () => { describe('mid-stream stop methods', () => { let sub1 let sub2 - let M let published beforeEach(async () => { - M = new MessageStream(client) sub1 = await pTimeout(M.subscribe(stream.id), 1500, 'subscribe 1') sub2 = await pTimeout(M.subscribe(stream.id), 1500, 'subscribe 2') }) @@ -522,7 +506,6 @@ describe('StreamrClient Stream', () => { ), 1500, `publish msg ${i}`) published.push(message) // eslint-disable-next-line no-await-in-loop - await wait(100) } }) @@ -598,7 +581,6 @@ describe('StreamrClient Stream', () => { }) it('will clean up if iterator returned before start', async () => { - const M = new MessageStream(client) const unsubscribeEvents = [] client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { unsubscribeEvents.push(m) @@ -624,7 +606,6 @@ describe('StreamrClient Stream', () => { client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { unsubscribeEvents.push(m) }) - const M = new MessageStream(client) const [sub] = await Promise.all([ M.subscribe(stream.id), M.unsubscribe(stream.id), From fe29aca22faed0aa748105a23762f60c933fd370 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 15 Oct 2020 15:51:02 -0400 Subject: [PATCH 097/517] Remove AbortController handling. Nest test setup inside loop. --- src/Stream.js | 49 +-- src/iterators.js | 81 +--- src/utils.js | 33 +- test/integration/Stream.test.js | 501 +++++++++---------------- test/integration/StreamResends.test.js | 245 ++++++------ 5 files changed, 342 insertions(+), 567 deletions(-) diff --git a/src/Stream.js b/src/Stream.js index 8b7608131..4d3fbbf91 100644 --- a/src/Stream.js +++ b/src/Stream.js @@ -3,11 +3,10 @@ import { PassThrough } from 'stream' import pMemoize from 'p-memoize' import pLimit from 'p-limit' import { ControlLayer, MessageLayer, Utils, Errors } from 'streamr-client-protocol' -import AbortController from 'node-abort-controller' import SignatureRequiredError from './errors/SignatureRequiredError' import { uuid, CacheAsyncFn, pOrderedResolve } from './utils' -import { endStream, pipeline, AbortError, CancelableGenerator } from './iterators' +import { endStream, pipeline, CancelableGenerator } from './iterators' const { OrderingUtil, StreamMessageValidator } = Utils @@ -20,8 +19,6 @@ const { const { MessageRef, StreamMessage } = MessageLayer -export { AbortError } - /** * Convert allSettled results into a thrown Aggregate error if necessary. */ @@ -268,23 +265,12 @@ function Validator(client, opts) { function MessagePipeline(client, opts = {}, onFinally = () => {}) { const options = validateOptions(opts) - const { signal, validate = Validator(client, options) } = options + const { validate = Validator(client, options) } = options + const stream = messageStream(client.connection, options) const orderingUtil = OrderMessages(client, options) - let p - const onAbort = () => { - p.cancel(new AbortError()) - } - - signal.addEventListener('abort', onAbort, { - once: true - }) - if (signal.aborted) { - stream.destroy(new AbortError()) - } - - p = pipeline([ + const p = pipeline([ stream, async function* Validate(src) { for await (const { streamMessage } of src) { @@ -301,9 +287,6 @@ function MessagePipeline(client, opts = {}, onFinally = () => {}) { }, orderingUtil, ], async (err) => { - signal.removeEventListener('abort', onAbort, { - once: true, - }) try { await endStream(stream, err) } finally { @@ -352,7 +335,13 @@ async function unsubscribe(client, { streamId, streamPartition = 0 }) { const onResponse = waitForRequestResponse(client, request) await client.send(request) - return onResponse + return onResponse.catch((err) => { + if (err.message.startsWith('Not subscribed to stream')) { + // noop if unsubscribe failed because we are already unsubscribed + return + } + throw err + }) } // @@ -419,12 +408,10 @@ async function resend(client, options) { async function getResendStream(client, opts) { const options = validateOptions(opts) - const abortController = new AbortController() const msgs = MessagePipeline(client, { ...options, type: ControlMessage.TYPES.UnicastMessage, - signal: abortController.signal, }) const requestId = uuid('rs') @@ -464,7 +451,6 @@ class Subscription { constructor(client, options) { this.client = client this.options = validateOptions(options) - this.abortController = new AbortController() this.streams = new Set() this.queue = pLimit(1) @@ -482,10 +468,6 @@ class Subscription { return !!(this.queue.activeCount || this.queue.pendingCount) } - async abort() { - await this.abortController.abort() - } - async sendSubscribe() { return subscribe(this.client, this.options) } @@ -541,7 +523,6 @@ class Subscription { iterate() { const msgs = MessagePipeline(this.client, { - signal: this.abortController.signal, validate: this.validate, type: ControlMessage.TYPES.BroadcastMessage, ...this.options, @@ -579,11 +560,6 @@ export default class Subscriptions { return this.subscriptions.get(key) } - abort(options) { - const sub = this.get(options) - return sub && sub.abort() - } - count(options) { const sub = this.get(options) return sub ? sub.count() : 0 @@ -640,9 +616,6 @@ export default class Subscriptions { return Object.assign(it, { realtime: sub, resend: resendSub, - abort: () => ( - this.abortController && this.abortController.abort() - ), }) } } diff --git a/src/iterators.js b/src/iterators.js index 653b2c99f..ddc81544e 100644 --- a/src/iterators.js +++ b/src/iterators.js @@ -3,45 +3,8 @@ import { promisify } from 'util' import pMemoize from 'p-memoize' -import { Defer } from './utils' +import { Defer, pTimeout } from './utils' -class TimeoutError extends Error { - constructor(msg = '', timeout = 0, ...args) { - super(`The operation timed out. ${timeout}ms. ${msg}`, ...args) - this.timeout = timeout - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor) - } - } -} - -export async function pTimeout(promise, timeout = 0, message = '') { - let t - return Promise.race([ - promise, - new Promise((resolve, reject) => { - t = setTimeout(() => { - reject(new TimeoutError(message, timeout)) - }, timeout) - }) - ]).finally(() => { - clearTimeout(t) - }) -} - -pTimeout.ignoreError = (err) => { - if (err instanceof TimeoutError) { return } - throw err -} - -export class AbortError extends Error { - constructor(msg = '', ...args) { - super(`The operation was aborted. ${msg}`, ...args) - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor) - } - } -} const pFinished = promisify(finished) export async function endStream(stream, optionalErr) { @@ -424,45 +387,3 @@ export function pipeline(iterables = [], onFinally, opts) { cancel, }) } - -/** - * Iterates over a Stream - * Cleans up stream/stops iterator if either stream or iterator ends. - * Adds abort + end methods to iterator - */ - -export function StreamIterator(stream, { abortController, onFinally = () => {}, } = {}) { - const onFinallyOnce = pMemoize(onFinally) // called once when stream ends - - const it = iteratorFinally((async function* StreamIteratorFn() { - yield* stream - }()), async () => { - try { - await endStream(stream) - } finally { - await onFinallyOnce() - } - }) - - return Object.assign(it, { - stream, - async abort() { - if (abortController) { - abortController.abort() - } else { - await it.end(new AbortError()) - } - }, - async end(optionalErr) { - try { - await endStream(stream, optionalErr) - } finally { - if (optionalErr) { - await it.throw(optionalErr) - } else { - await it.return() - } - } - } - }) -} diff --git a/src/utils.js b/src/utils.js index 51ed4e402..e21fcf6c4 100644 --- a/src/utils.js +++ b/src/utils.js @@ -178,10 +178,31 @@ export function pOrderedResolve(fn) { } } -export function pOrderedResolveLimit(limit = 6, fn) { - const queue = pLimit(limit) - const orderedFn = pOrderedResolve(fn) - return async (...args) => ( - queue(orderedFn(...args)) - ) +export class TimeoutError extends Error { + constructor(msg = '', timeout = 0, ...args) { + super(`The operation timed out. ${timeout}ms. ${msg}`, ...args) + this.timeout = timeout + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor) + } + } +} + +export async function pTimeout(promise, timeout = 0, message = '') { + let t + return Promise.race([ + promise, + new Promise((resolve, reject) => { + t = setTimeout(() => { + reject(new TimeoutError(message, timeout)) + }, timeout) + }) + ]).finally(() => { + clearTimeout(t) + }) +} + +pTimeout.ignoreError = (err) => { + if (err instanceof TimeoutError) { return } + throw err } diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index 5a2079654..e54a1cd65 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -1,11 +1,9 @@ import { ControlLayer } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' -import Debug from 'debug' import { uid, fakePrivateKey } from '../utils' import StreamrClient from '../../src' -import { pTimeout } from '../../src/iterators' -import { Defer } from '../../src/utils' +import { Defer, pTimeout } from '../../src/utils' import Connection from '../../src/Connection' import MessageStream from '../../src/Stream' @@ -31,106 +29,104 @@ async function collect(iterator, fn = () => {}) { } const TEST_REPEATS = 3 - -console.log = Debug('Streamr:: CONSOLE ') +const MAX_ITEMS = 2 describe('StreamrClient Stream', () => { - let expectErrors = 0 // check no errors by default - let onError = jest.fn() - let client - let stream - let M - - const createClient = (opts = {}) => { - const c = new StreamrClient({ - auth: { - privateKey: fakePrivateKey(), - }, - autoConnect: false, - autoDisconnect: false, - maxRetries: 2, - ...config.clientOptions, - ...opts, - }) - c.onError = jest.fn() - c.on('error', onError) - return c - } - - beforeAll(async () => { - if (client) { - await client.disconnect() - } - }) - - beforeEach(async () => { - expectErrors = 0 - onError = jest.fn() - }) - - beforeEach(async () => { - // eslint-disable-next-line require-atomic-updates - client = createClient() - M = new MessageStream(client) - await pTimeout(client.connect(), 4500, 'client.connect') - }) - - beforeEach(async () => { - stream = await pTimeout(client.createStream({ - name: uid('stream') - }), 10000, 'createStream') - - await wait(250) // prevent timing issues - }, 11000) - - afterEach(async () => { - await wait() - // ensure no unexpected errors - expect(onError).toHaveBeenCalledTimes(expectErrors) - if (client) { - expect(client.onError).toHaveBeenCalledTimes(expectErrors) - } - }) - - afterEach(async () => { - await wait() - if (client) { - client.debug('disconnecting after test') - await pTimeout(client.disconnect(), 4500, 'client.disconnect') - client.debug('disconnected after test') - } - - const openSockets = Connection.getOpen() - if (openSockets !== 0) { - throw new Error(`sockets not closed: ${openSockets}`) - } - }) - - it('attaches listener at subscribe time', async () => { - const beforeCount = client.connection.listenerCount(ControlMessage.TYPES.BroadcastMessage) - const sub = await M.subscribe(stream.id) - const afterCount = client.connection.listenerCount(ControlMessage.TYPES.BroadcastMessage) - expect(afterCount).toBeGreaterThan(beforeCount) - expect(M.count(stream.id)).toBe(1) - await sub.return() - expect(M.count(stream.id)).toBe(0) - }) - for (let k = 0; k < TEST_REPEATS; k++) { // eslint-disable-next-line no-loop-func describe(`test repeat ${k + 1} of ${TEST_REPEATS}`, () => { + let expectErrors = 0 // check no errors by default + let onError = jest.fn() + let client + let stream + let M + + const createClient = (opts = {}) => { + const c = new StreamrClient({ + auth: { + privateKey: fakePrivateKey(), + }, + autoConnect: false, + autoDisconnect: false, + maxRetries: 2, + ...config.clientOptions, + ...opts, + }) + c.onError = jest.fn() + c.on('error', onError) + return c + } + + beforeEach(async () => { + expectErrors = 0 + onError = jest.fn() + }) + + beforeEach(async () => { + // eslint-disable-next-line require-atomic-updates + client = createClient() + M = new MessageStream(client) + client.debug('connecting before test >>') + await Promise.all([ + client.connect(), + client.session.getSessionToken(), + ]) + stream = await client.createStream({ + name: uid('stream') + }) + client.debug('connecting before test <<') + }) + + afterEach(async () => { + await wait() + // ensure no unexpected errors + expect(onError).toHaveBeenCalledTimes(expectErrors) + if (client) { + expect(client.onError).toHaveBeenCalledTimes(expectErrors) + } + }) + + afterEach(async () => { + await wait() + if (client) { + client.debug('disconnecting after test >>') + await client.disconnect() + client.debug('disconnecting after test <<') + } + + const openSockets = Connection.getOpen() + if (openSockets !== 0) { + throw new Error(`sockets not closed: ${openSockets}`) + } + }) + + async function publishTestMessages(n = 4, streamId = stream.id) { + const published = [] + for (let i = 0; i < n; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop, no-loop-func + await pTimeout(client.publish(streamId, message), 1500, `publish timeout ${streamId}: ${i} ${JSON.stringify(message)}`) + published.push(message) + } + return published + } + + it('attaches listener at subscribe time', async () => { + const beforeCount = client.connection.listenerCount(ControlMessage.TYPES.BroadcastMessage) + const sub = await M.subscribe(stream.id) + const afterCount = client.connection.listenerCount(ControlMessage.TYPES.BroadcastMessage) + expect(afterCount).toBeGreaterThan(beforeCount) + expect(M.count(stream.id)).toBe(1) + await sub.return() + expect(M.count(stream.id)).toBe(0) + }) + describe('basics', () => { it('can subscribe to stream and get updates then auto unsubscribe', async () => { const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) - const published = [] - for (let i = 0; i < 3; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + const published = await publishTestMessages() const received = [] for await (const m of sub) { @@ -147,13 +143,8 @@ describe('StreamrClient Stream', () => { const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) - const published = [] - for (let i = 0; i < 3; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + + const published = await publishTestMessages() const received = [] for await (const m of sub) { @@ -162,6 +153,7 @@ describe('StreamrClient Stream', () => { return } } + expect(received).toEqual(published) expect(M.count(stream.id)).toBe(0) }) @@ -170,14 +162,7 @@ describe('StreamrClient Stream', () => { const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) - const published = [] - for (let i = 0; i < 3; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } - + await publishTestMessages() let t let expectedLength const received = [] @@ -210,13 +195,7 @@ describe('StreamrClient Stream', () => { const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) - const published = [] - for (let i = 0; i < 3; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + await publishTestMessages() const err = new Error('expected error') const received = [] @@ -224,7 +203,7 @@ describe('StreamrClient Stream', () => { for await (const m of sub) { received.push(m) // after first message schedule end - if (received.length) { + if (received.length === 1) { throw err } } @@ -235,61 +214,23 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) }) - it('can kill stream with abort', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) - const sub = await M.subscribe(stream.id) - expect(M.count(stream.id)).toBe(1) - - const published = [] - for (let i = 0; i < 3; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } - - const received = [] - await expect(async () => { - for await (const m of sub) { - received.push(m) - // after first message schedule end - if (received.length === 1) { - await M.abort(stream.id) - } - } - }).rejects.toThrow('abort') - // gets some messages but not all - expect(received).toHaveLength(1) - expect(unsubscribeEvents).toHaveLength(1) - expect(M.count(stream.id)).toBe(0) - }) - it('can subscribe to stream multiple times, get updates then unsubscribe', async () => { const sub1 = await M.subscribe(stream.id) const sub2 = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(2) - const published = [] - for (let i = 0; i < 3; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - // eslint-disable-next-line no-await-in-loop - } + + const published = await publishTestMessages() const [received1, received2] = await Promise.all([ - collect(sub1, ({ received, iterator }) => { + collect(sub1, async ({ received, iterator }) => { if (received.length === published.length) { - iterator.return() + await iterator.return() } }), - collect(sub2, ({ received, iterator }) => { + collect(sub2, async ({ received, iterator }) => { if (received.length === published.length) { - iterator.return() + await iterator.return() } }) ]) @@ -306,23 +247,17 @@ describe('StreamrClient Stream', () => { ]) expect(M.count(stream.id)).toBe(2) - const published = [] - for (let i = 0; i < 3; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + const published = await publishTestMessages() const [received1, received2] = await Promise.all([ - collect(sub1, ({ received, iterator }) => { + collect(sub1, async ({ received, iterator }) => { if (received.length === published.length) { - iterator.return() + await iterator.return() } }), - collect(sub2, ({ received, iterator }) => { + collect(sub2, async ({ received, iterator }) => { if (received.length === published.length) { - iterator.return() + await iterator.return() } }) ]) @@ -336,13 +271,7 @@ describe('StreamrClient Stream', () => { const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) - const published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + const published = await publishTestMessages() const received = [] for await (const m of sub) { @@ -364,24 +293,20 @@ describe('StreamrClient Stream', () => { const sub = await M.subscribe(stream.id) - const published = [] - for (let i = 0; i < 3; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + const published = await publishTestMessages() + const received = [] for await (const m of sub) { received.push(m.getParsedContent()) - if (received.length === 2) { + if (received.length === MAX_ITEMS) { expect(unsubscribeEvents).toHaveLength(0) await sub.return() expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) } } - expect(received).toHaveLength(2) + expect(received).toHaveLength(MAX_ITEMS) + expect(received).toEqual(published.slice(0, MAX_ITEMS)) expect(M.count(stream.id)).toBe(0) }) @@ -393,28 +318,24 @@ describe('StreamrClient Stream', () => { const sub = await M.subscribe(stream.id) - const published = [] - for (let i = 0; i < 3; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + const published = await publishTestMessages() + const received = [] for await (const m of sub) { received.push(m.getParsedContent()) - if (received.length === 2) { + if (received.length === MAX_ITEMS) { expect(unsubscribeEvents).toHaveLength(0) await sub.cancel() expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) } } - expect(received).toHaveLength(2) + expect(received).toHaveLength(MAX_ITEMS) + expect(received).toEqual(published.slice(0, MAX_ITEMS)) expect(M.count(stream.id)).toBe(0) }) - it('can end + return and it will wait for unsubscribe', async () => { + it('can cancel + return and it will wait for unsubscribe', async () => { const unsubscribeEvents = [] client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { unsubscribeEvents.push(m) @@ -422,17 +343,12 @@ describe('StreamrClient Stream', () => { const sub = await M.subscribe(stream.id) - const published = [] - for (let i = 0; i < 3; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + const published = await publishTestMessages() + const received = [] for await (const m of sub) { received.push(m.getParsedContent()) - if (received.length === 2) { + if (received.length === MAX_ITEMS) { expect(unsubscribeEvents).toHaveLength(0) const tasks = [ sub.return(), @@ -445,11 +361,12 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) } } - expect(received).toHaveLength(2) + expect(received).toHaveLength(MAX_ITEMS) + expect(received).toEqual(published.slice(0, MAX_ITEMS)) expect(M.count(stream.id)).toBe(0) }) - it('can end + multiple times and it will wait for unsubscribe', async () => { + it('can cancel multiple times and it will wait for unsubscribe', async () => { const unsubscribeEvents = [] client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { unsubscribeEvents.push(m) @@ -457,17 +374,12 @@ describe('StreamrClient Stream', () => { const sub = await M.subscribe(stream.id) - const published = [] - for (let i = 0; i < 3; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } + const published = await publishTestMessages() + const received = [] for await (const m of sub) { received.push(m.getParsedContent()) - if (received.length === 2) { + if (received.length === MAX_ITEMS) { expect(unsubscribeEvents).toHaveLength(0) const tasks = [ sub.cancel(), @@ -481,7 +393,54 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) } } - expect(received).toHaveLength(2) + expect(received).toHaveLength(MAX_ITEMS) + expect(received).toEqual(published.slice(0, MAX_ITEMS)) + expect(M.count(stream.id)).toBe(0) + }) + + it('will clean up if iterator returned before start', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) + + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) + await sub.return() + expect(M.count(stream.id)).toBe(0) + + await publishTestMessages() + + const received = [] + for await (const m of sub) { + received.push(m.getParsedContent()) + } + expect(received).toHaveLength(0) + expect(unsubscribeEvents).toHaveLength(1) + + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe then unsubscribe in parallel', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) + const [sub] = await Promise.all([ + M.subscribe(stream.id), + M.unsubscribe(stream.id), + ]) + + await publishTestMessages() + + const received = [] + for await (const m of sub) { + received.push(m.getParsedContent()) + } + + // shouldn't get any messages + expect(received).toHaveLength(0) + expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) }) }) @@ -492,21 +451,9 @@ describe('StreamrClient Stream', () => { let published beforeEach(async () => { - sub1 = await pTimeout(M.subscribe(stream.id), 1500, 'subscribe 1') - sub2 = await pTimeout(M.subscribe(stream.id), 1500, 'subscribe 2') - }) - - beforeEach(async () => { - published = [] - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await pTimeout(( - client.publish(stream.id, message) - ), 1500, `publish msg ${i}`) - published.push(message) - // eslint-disable-next-line no-await-in-loop - } + sub1 = await M.subscribe(stream.id) + sub2 = await M.subscribe(stream.id) + published = await publishTestMessages(5) }) it('can subscribe to stream multiple times then unsubscribe all mid-stream', async () => { @@ -520,115 +467,37 @@ describe('StreamrClient Stream', () => { }), collect(sub2, async ({ received }) => { await gotOne - if (received.length === 1) { + if (received.length === MAX_ITEMS) { sub1ReceivedAtUnsubscribe = sub1Received.slice() await M.unsubscribe(stream.id) } }), ]) - expect(received2).toEqual(published.slice(0, 1)) + expect(received2).toEqual(published.slice(0, MAX_ITEMS)) expect(received1).toEqual(published.slice(0, sub1ReceivedAtUnsubscribe.length)) expect(sub1ReceivedAtUnsubscribe).toEqual(sub1Received) expect(M.count(stream.id)).toBe(0) }) - it('can subscribe to stream multiple times then abort mid-stream', async () => { - let received1 - let received2 - let sub1ReceivedAtUnsubscribe - const gotOne = Defer() - await Promise.all([ - expect(() => collect(sub1, ({ received }) => { - received1 = received - gotOne.resolve() - })).rejects.toThrow('abort'), - expect(() => collect(sub2, async ({ received }) => { - await gotOne - received2 = received - if (received.length === 1) { - sub1ReceivedAtUnsubscribe = received1.slice() - await M.abort(stream.id) - } - })).rejects.toThrow('abort'), - ]) - - expect(received1).toEqual(sub1ReceivedAtUnsubscribe) - expect(received1).toEqual(published.slice(0, sub1ReceivedAtUnsubscribe.length)) - expect(received2).toEqual(published.slice(0, 1)) - - expect(M.count(stream.id)).toBe(0) - }) - it('can subscribe to stream multiple times then return mid-stream', async () => { const [received1, received2] = await Promise.all([ collect(sub1, async ({ received, iterator }) => { - if (received.length === 4) { + if (received.length === MAX_ITEMS - 1) { await iterator.return() } }), collect(sub2, async ({ received, iterator }) => { - if (received.length === 1) { + if (received.length === MAX_ITEMS) { await iterator.return() } - }) + }), ]) - expect(received1).toEqual(published.slice(0, 4)) - expect(received2).toEqual(published.slice(0, 1)) - + expect(received1).toEqual(published.slice(0, MAX_ITEMS - 1)) + expect(received2).toEqual(published.slice(0, MAX_ITEMS)) expect(M.count(stream.id)).toBe(0) }) }) - - it('will clean up if iterator returned before start', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) - - const sub = await M.subscribe(stream.id) - expect(M.count(stream.id)).toBe(1) - await sub.return() - expect(M.count(stream.id)).toBe(0) - - const received = [] - for await (const m of sub) { - received.push(m.getParsedContent()) - } - expect(received).toHaveLength(0) - expect(unsubscribeEvents).toHaveLength(1) - - expect(M.count(stream.id)).toBe(0) - }) - - it('can subscribe then unsubscribe in parallel', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) - const [sub] = await Promise.all([ - M.subscribe(stream.id), - M.unsubscribe(stream.id), - ]) - - const published = [] - for (let i = 0; i < 2; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - } - - const received = [] - for await (const m of sub) { - received.push(m.getParsedContent()) - } - - // shouldn't get any messages - expect(received).toHaveLength(0) - expect(unsubscribeEvents).toHaveLength(1) - expect(M.count(stream.id)).toBe(0) - }) }) } }) diff --git a/test/integration/StreamResends.test.js b/test/integration/StreamResends.test.js index 195011d5a..42ab3b5cf 100644 --- a/test/integration/StreamResends.test.js +++ b/test/integration/StreamResends.test.js @@ -28,139 +28,141 @@ async function collect(iterator, fn = () => {}) { return received } -const TEST_REPEATS = 3 +const TEST_REPEATS = 2 /* eslint-disable no-await-in-loop */ const WAIT_FOR_STORAGE_TIMEOUT = 6000 describe('resends', () => { - let expectErrors = 0 // check no errors by default - let onError = jest.fn() - let client - let stream - let published - - const createClient = (opts = {}) => { - const c = new StreamrClient({ - auth: { - privateKey: fakePrivateKey(), - }, - autoConnect: false, - autoDisconnect: false, - maxRetries: 2, - ...config.clientOptions, - ...opts, - }) - c.onError = jest.fn() - c.on('error', onError) - return c - } - - async function waitForStorage({ streamId, streamPartition = 0, msg, timeout = 5000 }) { - const start = Date.now() - let last - // eslint-disable-next-line no-constant-condition - while (true) { - const duration = Date.now() - start - if (duration > timeout) { - client.debug('waitForStorage timeout %o', { - timeout, - duration - }, { - msg, - last: last.map((l) => l.content), + for (let k = 0; k < TEST_REPEATS; k++) { + // eslint-disable-next-line no-loop-func + describe(`test repeat ${k + 1} of ${TEST_REPEATS}`, () => { + let expectErrors = 0 // check no errors by default + let onError = jest.fn() + let client + let stream + let published + let M + + const createClient = (opts = {}) => { + const c = new StreamrClient({ + auth: { + privateKey: fakePrivateKey(), + }, + autoConnect: false, + autoDisconnect: false, + maxRetries: 2, + ...config.clientOptions, + ...opts, }) - const err = new Error(`timed out after ${duration}ms waiting for message`) - err.msg = msg - throw err + c.onError = jest.fn() + c.on('error', onError) + return c } - last = await client.getStreamLast({ - streamId, - streamPartition, - count: 3, - }) + async function waitForStorage({ streamId, streamPartition = 0, msg, timeout = 5000 }) { + const start = Date.now() + let last + // eslint-disable-next-line no-constant-condition + while (true) { + const duration = Date.now() - start + if (duration > timeout) { + client.debug('waitForStorage timeout %o', { + timeout, + duration + }, { + msg, + last: last.map((l) => l.content), + }) + const err = new Error(`timed out after ${duration}ms waiting for message`) + err.msg = msg + throw err + } + + last = await client.getStreamLast({ + streamId, + streamPartition, + count: 3, + }) + + let found = false + for (const { content } of last) { + if (content.value === msg.value) { + found = true + break + } + } + + if (found) { + return + } - let found = false - for (const { content } of last) { - if (content.value === msg.value) { - found = true - break + client.debug('message not found, retrying... %o', { + msg, last: last.map(({ content }) => content) + }) + await wait(500) } } - if (found) { - return - } + beforeAll(async () => { + // eslint-disable-next-line require-atomic-updates + client = createClient() + await client.session.getSessionToken() + + M = new MessageStream(client) + stream = await client.createStream({ + name: uid('stream'), + }) - client.debug('message not found, retrying... %o', { - msg, last: last.map(({ content }) => content) + published = [] + await client.connect() + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + await wait(50) + } }) - await wait(500) - } - } - beforeAll(async () => { - // eslint-disable-next-line require-atomic-updates - client = createClient() - stream = await client.createStream({ - name: uid('stream'), - }) + beforeAll(async () => { + const lastMessage = published[published.length - 1] + await waitForStorage({ + msg: lastMessage, + timeout: WAIT_FOR_STORAGE_TIMEOUT, + streamId: stream.id, + }) + }, WAIT_FOR_STORAGE_TIMEOUT * 2) - await wait(2000) - }) - - beforeAll(async () => { - published = [] - await client.connect() - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - await wait(50) - } - - const lastMessage = published[published.length - 1] - await waitForStorage({ - msg: lastMessage, - timeout: WAIT_FOR_STORAGE_TIMEOUT, - streamId: stream.id, - }) - }, WAIT_FOR_STORAGE_TIMEOUT * 2) - - beforeEach(async () => { - await client.connect() - expectErrors = 0 - onError = jest.fn() - }) - - afterEach(async () => { - await wait() - // ensure no unexpected errors - expect(onError).toHaveBeenCalledTimes(expectErrors) - if (client) { - expect(client.onError).toHaveBeenCalledTimes(expectErrors) - } - }) - - afterAll(async () => { - await wait(500) - if (client) { - client.debug('disconnecting after test') - await client.disconnect() - } - - const openSockets = Connection.getOpen() - if (openSockets !== 0) { - throw new Error(`sockets not closed: ${openSockets}`) - } - }) + beforeEach(async () => { + await client.connect() + expectErrors = 0 + onError = jest.fn() + }) + + afterEach(async () => { + await wait() + // ensure no unexpected errors + expect(onError).toHaveBeenCalledTimes(expectErrors) + if (client) { + expect(client.onError).toHaveBeenCalledTimes(expectErrors) + } + }) + + afterEach(async () => { + await wait(500) + if (client) { + client.debug('disconnecting after test') + await client.disconnect() + } + + const openSockets = Connection.getOpen() + if (openSockets !== 0) { + throw new Error(`sockets not closed: ${openSockets}`) + } + }) - for (let k = 0; k < TEST_REPEATS; k++) { - // eslint-disable-next-line no-loop-func - describe(`test repeat ${k + 1} of ${TEST_REPEATS}`, () => { describe('no data', () => { let emptyStream @@ -170,7 +172,6 @@ describe('resends', () => { }) await wait(3000) - const M = new MessageStream(client) const sub = await M.resend({ streamId: emptyStream.id, last: 5, @@ -182,8 +183,6 @@ describe('resends', () => { }) it('resendSubscribe with nothing to resend', async () => { - const M = new MessageStream(client) - emptyStream = await client.createStream({ name: uid('stream') }) @@ -219,8 +218,7 @@ describe('resends', () => { }) }, WAIT_FOR_STORAGE_TIMEOUT * 1.2) - it('requests resend', async () => { - const M = new MessageStream(client) + it.only('requests resend', async () => { const sub = await M.resend({ streamId: stream.id, last: published.length, @@ -232,7 +230,6 @@ describe('resends', () => { }) it('requests resend number', async () => { - const M = new MessageStream(client) const sub = await M.resend({ streamId: stream.id, last: 2, @@ -245,7 +242,6 @@ describe('resends', () => { }) it('closes stream', async () => { - const M = new MessageStream(client) const sub = await M.resend({ streamId: stream.id, last: published.length, @@ -263,7 +259,6 @@ describe('resends', () => { describe('resendSubscribe', () => { it('sees resends and realtime', async () => { - const M = new MessageStream(client) const sub = await M.resendSubscribe({ streamId: stream.id, last: published.length, @@ -291,7 +286,6 @@ describe('resends', () => { }) it('sees resends and realtime again', async () => { - const M = new MessageStream(client) const sub = await M.resendSubscribe({ streamId: stream.id, last: published.length, @@ -319,7 +313,6 @@ describe('resends', () => { }) it('can return before start', async () => { - const M = new MessageStream(client) const sub = await M.resendSubscribe({ streamId: stream.id, last: published.length, @@ -345,7 +338,6 @@ describe('resends', () => { }) it('can end asynchronously', async () => { - const M = new MessageStream(client) const sub = await M.resendSubscribe({ streamId: stream.id, last: published.length, @@ -384,7 +376,6 @@ describe('resends', () => { client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { unsubscribeEvents.push(m) }) - const M = new MessageStream(client) const sub = await M.resendSubscribe({ streamId: stream.id, last: published.length, From 10e72f0a198df2d7d742de212d58af61502078b3 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 16 Oct 2020 11:30:56 -0400 Subject: [PATCH 098/517] Move subscription under src/subscription/, utils under src/utils/ --- src/StreamrClient.js | 4 +--- src/rest/StreamEndpoints.js | 4 ++-- src/{Stream.js => subscribe/index.js} | 6 +++--- src/{utils.js => utils/index.js} | 3 +-- src/{ => utils}/iterators.js | 2 +- test/integration/Stream.test.js | 2 +- test/integration/StreamResends.test.js | 4 ++-- test/unit/iterators.test.js | 2 +- 8 files changed, 12 insertions(+), 15 deletions(-) rename src/{Stream.js => subscribe/index.js} (98%) rename src/{utils.js => utils/index.js} (98%) rename src/{ => utils}/iterators.js (99%) diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 8ac27f3c7..815565951 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -9,9 +9,7 @@ import Connection from './Connection' import Session from './Session' import { getVersionString } from './utils' import Publisher from './Publisher' -import Resender from './Resender' -import Subscriber from './Subscriber' -import MessageStream from './Stream' +import MessageStream from './subscribe' const { ControlMessage } = ControlLayer diff --git a/src/rest/StreamEndpoints.js b/src/rest/StreamEndpoints.js index 874f73fc3..4a861db5c 100644 --- a/src/rest/StreamEndpoints.js +++ b/src/rest/StreamEndpoints.js @@ -4,12 +4,12 @@ import { Agent as HttpsAgent } from 'https' import qs from 'qs' import debugFactory from 'debug' -import { validateOptions, getEndpointUrl } from '../Stream' +import { validateOptions } from '../subscribe' +import { getEndpointUrl } from '../utils' import Stream from './domain/Stream' import authFetch from './authFetch' - const debug = debugFactory('StreamrClient') const agentSettings = { diff --git a/src/Stream.js b/src/subscribe/index.js similarity index 98% rename from src/Stream.js rename to src/subscribe/index.js index 4d3fbbf91..8b2c89ed0 100644 --- a/src/Stream.js +++ b/src/subscribe/index.js @@ -4,9 +4,9 @@ import pMemoize from 'p-memoize' import pLimit from 'p-limit' import { ControlLayer, MessageLayer, Utils, Errors } from 'streamr-client-protocol' -import SignatureRequiredError from './errors/SignatureRequiredError' -import { uuid, CacheAsyncFn, pOrderedResolve } from './utils' -import { endStream, pipeline, CancelableGenerator } from './iterators' +import SignatureRequiredError from '../errors/SignatureRequiredError' +import { uuid, CacheAsyncFn, pOrderedResolve } from '../utils' +import { endStream, pipeline, CancelableGenerator } from '../utils/iterators' const { OrderingUtil, StreamMessageValidator } = Utils diff --git a/src/utils.js b/src/utils/index.js similarity index 98% rename from src/utils.js rename to src/utils/index.js index e21fcf6c4..a183ece0b 100644 --- a/src/utils.js +++ b/src/utils/index.js @@ -2,11 +2,10 @@ import { v4 as uuidv4 } from 'uuid' import uniqueId from 'lodash.uniqueid' import LRU from 'quick-lru' import pMemoize from 'p-memoize' -import pQueue from 'p-queue' import pLimit from 'p-limit' import mem from 'mem' -import pkg from '../package.json' +import pkg from '../../package.json' const UUID = uuidv4() diff --git a/src/iterators.js b/src/utils/iterators.js similarity index 99% rename from src/iterators.js rename to src/utils/iterators.js index ddc81544e..6ef514c3f 100644 --- a/src/iterators.js +++ b/src/utils/iterators.js @@ -3,7 +3,7 @@ import { promisify } from 'util' import pMemoize from 'p-memoize' -import { Defer, pTimeout } from './utils' +import { Defer, pTimeout } from '../utils' const pFinished = promisify(finished) diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index e54a1cd65..4125e9e06 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -5,7 +5,7 @@ import { uid, fakePrivateKey } from '../utils' import StreamrClient from '../../src' import { Defer, pTimeout } from '../../src/utils' import Connection from '../../src/Connection' -import MessageStream from '../../src/Stream' +import MessageStream from '../../src/subscribe' import config from './config' diff --git a/test/integration/StreamResends.test.js b/test/integration/StreamResends.test.js index 42ab3b5cf..4cac0ae2c 100644 --- a/test/integration/StreamResends.test.js +++ b/test/integration/StreamResends.test.js @@ -4,7 +4,7 @@ import { wait } from 'streamr-test-utils' import { uid, fakePrivateKey } from '../utils' import StreamrClient from '../../src' import Connection from '../../src/Connection' -import MessageStream from '../../src/Stream' +import MessageStream from '../../src/subscribe' import config from './config' @@ -218,7 +218,7 @@ describe('resends', () => { }) }, WAIT_FOR_STORAGE_TIMEOUT * 1.2) - it.only('requests resend', async () => { + it('requests resend', async () => { const sub = await M.resend({ streamId: stream.id, last: published.length, diff --git a/test/unit/iterators.test.js b/test/unit/iterators.test.js index 44bd9165a..41192e25b 100644 --- a/test/unit/iterators.test.js +++ b/test/unit/iterators.test.js @@ -2,7 +2,7 @@ import { Readable, PassThrough } from 'stream' import { wait } from 'streamr-test-utils' -import { iteratorFinally, CancelableGenerator, pipeline } from '../../src/iterators' +import { iteratorFinally, CancelableGenerator, pipeline } from '../../src/utils/iterators' import { Defer } from '../../src/utils' const expected = [1, 2, 3, 4, 5, 6, 7, 8] From 8076f7c4d5413ed1318e4817ad24c7bba068c379 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 16 Oct 2020 11:42:18 -0400 Subject: [PATCH 099/517] Remove agent from authFetch for now. --- src/Connection.js | 3 --- src/rest/authFetch.js | 28 ---------------------------- 2 files changed, 31 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index 34e3adabc..23442d6c7 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -3,8 +3,6 @@ import Debug from 'debug' import uniqueId from 'lodash.uniqueid' import WebSocket from 'ws' -import { getAgent } from './rest/authFetch' - // add global support for pretty millisecond formatting with %n Debug.formatters.n = (v) => Debug.humanize(v) @@ -370,7 +368,6 @@ export default class Connection extends EventEmitter { const socket = await OpenWebSocket(this.options.url, { perMessageDeflate: false, - agent: getAgent(this.options.url.startsWith('wss:') ? 'https:' : 'http:'), }) debug('connected') if (!this.shouldConnect) { diff --git a/src/rest/authFetch.js b/src/rest/authFetch.js index 0a7b3bb71..2e7656007 100644 --- a/src/rest/authFetch.js +++ b/src/rest/authFetch.js @@ -1,6 +1,3 @@ -import http from 'http' -import https from 'https' - import fetch from 'node-fetch' import Debug from 'debug' @@ -11,30 +8,6 @@ export const DEFAULT_HEADERS = { 'Streamr-Client': `streamr-client-javascript/${getVersionString()}`, } -export function getAgent(protocol) { - /* eslint-disable consistent-return */ - if (process.browser) { - return - } - - if (protocol === 'http:') { - if (!getAgent.httpAgent) { - getAgent.httpAgent = new http.Agent({ - keepAlive: true, - }) - } - return getAgent.httpAgent - } - - if (!getAgent.httpsAgent) { - getAgent.httpsAgent = new https.Agent({ - keepAlive: true, - }) - } - return getAgent.httpsAgent - /* eslint-enable consistent-return */ -} - const debug = Debug('StreamrClient:utils:authfetch') let ID = 0 @@ -50,7 +23,6 @@ export default async function authFetch(url, session, opts, requireNewToken = fa ...DEFAULT_HEADERS, ...(opts && opts.headers), }, - agent: ({ protocol }) => getAgent(protocol), } // add default 'Content-Type: application/json' header for all POST and PUT requests if (!options.headers['Content-Type'] && (options.method === 'POST' || options.method === 'PUT')) { From 1f773c8964984abc58971c6b06188c6c5329ae6a Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 16 Oct 2020 16:03:35 -0400 Subject: [PATCH 100/517] Add onTransition to connection. --- src/Connection.js | 49 +++++++++++++++++++++++++++++++++--- test/unit/Connection.test.js | 44 ++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index 23442d6c7..4e76496b7 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -477,6 +477,8 @@ export default class Connection extends EventEmitter { if (this.shouldConnect) { throw new ConnectionError('connect before disconnected') } + + this.emit('done') } async nextConnection() { @@ -517,13 +519,13 @@ export default class Connection extends EventEmitter { } } - async needsConnection() { + async needsConnection(msg) { await this.maybeConnect() if (!this.isConnected()) { // note we can't just let socket.send fail, // have to do this check ourselves because the error appears // to be uncatchable in the browser - throw new ConnectionError('needs connection but connection closed or closing') + throw new ConnectionError(`needs connection but connection ${this.getState()}. ${msg}`) } } @@ -551,7 +553,8 @@ export default class Connection extends EventEmitter { this.debug('send()') if (!this.isConnected()) { // shortcut await if connected - await this.needsConnection() + const data = typeof msg.serialize === 'function' ? msg.serialize() : msg + await this.needsConnection(`sending ${data.slice(0, 1024)}...`) } this.debug('>> %o', msg) return this._send(msg) @@ -611,6 +614,46 @@ export default class Connection extends EventEmitter { } return this.socket.readyState === WebSocket.CONNECTING } + + onTransition({ + onConnected = () => {}, + onConnecting = () => {}, + onDisconnecting = () => {}, + onDisconnected = () => {}, + onDone = () => {}, + onError, + }) { + let onDoneHandler + const cleanUp = async (...args) => { + this + .off('connecting', onConnecting) + .off('connected', onConnected) + .off('disconnecting', onDisconnecting) + .off('disconnected', onDisconnected) + .off('done', onDoneHandler) + if (onError) { + this.off('error', onError) + } + } + + onDoneHandler = async (...args) => { + cleanUp(...args) + return onDone(...args) + } + + this + .on('connecting', onConnecting) + .on('connected', onConnected) + .on('disconnecting', onDisconnecting) + .on('disconnected', onDisconnected) + .on('done', onDoneHandler) + + if (onError) { + this.on('error', onError) + } + + return cleanUp + } } Connection.ConnectionError = ConnectionError diff --git a/test/unit/Connection.test.js b/test/unit/Connection.test.js index 159743794..2dc493cec 100644 --- a/test/unit/Connection.test.js +++ b/test/unit/Connection.test.js @@ -686,5 +686,49 @@ describe('Connection', () => { expect(received).toEqual(['test']) }) }) + + describe('onTransition', () => { + it('runs functions', async () => { + const onError = jest.fn() + const transitionFns = { + onConnected: jest.fn(), + onConnecting: jest.fn(), + onDisconnected: jest.fn(), + onDisconnecting: jest.fn(), + onDone: jest.fn(), + onError: jest.fn(), + } + + s.onTransition(transitionFns) + + await s.connect() + expect(transitionFns.onConnecting).toHaveBeenCalledTimes(1) + expect(transitionFns.onConnected).toHaveBeenCalledTimes(1) + s.socket.close() + await s.nextConnection() + expect(transitionFns.onDisconnecting).toHaveBeenCalledTimes(0) + expect(transitionFns.onDisconnected).toHaveBeenCalledTimes(1) + expect(transitionFns.onConnecting).toHaveBeenCalledTimes(2) + expect(transitionFns.onConnected).toHaveBeenCalledTimes(2) + expect(transitionFns.onDone).toHaveBeenCalledTimes(0) + await s.disconnect() + expect(transitionFns.onConnecting).toHaveBeenCalledTimes(2) + expect(transitionFns.onConnected).toHaveBeenCalledTimes(2) + expect(transitionFns.onDisconnecting).toHaveBeenCalledTimes(1) + expect(transitionFns.onDisconnected).toHaveBeenCalledTimes(2) + expect(transitionFns.onDone).toHaveBeenCalledTimes(1) + expect(transitionFns.onError).toHaveBeenCalledTimes(0) + + await s.connect() + // no more fired after disconnect done + expect(transitionFns.onConnecting).toHaveBeenCalledTimes(2) + expect(transitionFns.onConnected).toHaveBeenCalledTimes(2) + expect(transitionFns.onDisconnecting).toHaveBeenCalledTimes(1) + expect(transitionFns.onDisconnected).toHaveBeenCalledTimes(2) + expect(transitionFns.onDone).toHaveBeenCalledTimes(1) + expect(transitionFns.onError).toHaveBeenCalledTimes(0) + }) + }) + }) From 9558347b8268545d8bf9170895508a9d1547e528 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 16 Oct 2020 17:01:30 -0400 Subject: [PATCH 101/517] Add done event to connection after user disconnect or failed connect. --- src/Connection.js | 30 ++++++++++++++--- test/unit/Connection.test.js | 63 +++++++++++++++++++++++++----------- 2 files changed, 69 insertions(+), 24 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index 4e76496b7..794e69511 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -246,6 +246,7 @@ export default class Connection extends EventEmitter { }) })().finally(() => { if (this.reconnectTask === reconnectTask) { + this._isReconnecting = false this.reconnectTask = undefined } }) @@ -255,9 +256,11 @@ export default class Connection extends EventEmitter { onConnectError(error) { const err = new ConnectionError(error) + this.checkDone() if (!this._isReconnecting) { this.emit('error', err) } + throw err } @@ -265,14 +268,24 @@ export default class Connection extends EventEmitter { const err = new ConnectionError(error) // no check for reconnecting this.emit('error', err) + throw err } + checkDone() { + if (!this.isDone && !this._isReconnecting && !this.disconnectTask) { + this.isDone = true + this.shouldConnect = false + this.emit('done') + } + } + async connect() { - this.shouldConnect = true if (this.initialConnectTask) { return this.initialConnectTask } + this.shouldConnect = true + this.isDone = false const initialConnectTask = this._connectOnce() .catch((err) => { if (this.initialConnectTask === initialConnectTask) { @@ -339,6 +352,7 @@ export default class Connection extends EventEmitter { // eslint-disable-next-line promise/no-nesting await this.reconnect().catch((err) => { this.debug('failed reconnect after connected') + this.checkDone() this.emit('error', new ConnectionError(err)) }) }) @@ -436,22 +450,30 @@ export default class Connection extends EventEmitter { async disconnect() { this.options.autoConnect = false // reset auto-connect on manual disconnect this.shouldConnect = false + this._isReconnecting = false if (this.disconnectTask) { return this.disconnectTask } + let hadError = false const disconnectTask = this._disconnect() - .catch(this.onDisconnectError) + .catch((err) => { + hadError = true + return this.onDisconnectError(err) + }) .finally(() => { if (this.disconnectTask === disconnectTask) { this.disconnectTask = undefined + if (!hadError) { + this.checkDone() + } } + }) this.disconnectTask = disconnectTask return this.disconnectTask } async _disconnect() { - this.debug('disconnect()') if (this.connectTask) { try { await this.connectTask @@ -477,8 +499,6 @@ export default class Connection extends EventEmitter { if (this.shouldConnect) { throw new ConnectionError('connect before disconnected') } - - this.emit('done') } async nextConnection() { diff --git a/test/unit/Connection.test.js b/test/unit/Connection.test.js index 2dc493cec..94f8f3c90 100644 --- a/test/unit/Connection.test.js +++ b/test/unit/Connection.test.js @@ -14,6 +14,7 @@ describe('Connection', () => { let onDisconnecting let onDisconnected let onReconnecting + let onDone let onError let onMessage @@ -31,6 +32,8 @@ describe('Connection', () => { s.on('connecting', onConnecting) onDisconnected = jest.fn() s.on('disconnected', onDisconnected) + onDone = jest.fn() + s.on('done', onDone) onDisconnecting = jest.fn() s.on('disconnecting', onDisconnecting) onReconnecting = jest.fn() @@ -143,6 +146,7 @@ describe('Connection', () => { // check events expect(onConnected).toHaveBeenCalledTimes(2) expect(onDisconnected).toHaveBeenCalledTimes(1) + expect(onDone).toHaveBeenCalledTimes(1) // ensure new socket expect(s.socket).not.toBe(oldSocket) }) @@ -165,6 +169,7 @@ describe('Connection', () => { s.once('connecting', async () => { await s.disconnect() expect(s.isDisconnected()).toBeTruthy() + expect(onDone).toHaveBeenCalledTimes(1) done() }) await expect(async () => { @@ -178,6 +183,7 @@ describe('Connection', () => { s.once('connected', async () => { await s.disconnect() expect(s.isDisconnected()).toBeTruthy() + expect(onDone).toHaveBeenCalledTimes(1) done() }) @@ -197,6 +203,7 @@ describe('Connection', () => { s.once('disconnected', async () => { s.once('connected', async () => { await s.disconnect() + expect(onDone).toHaveBeenCalledTimes(1) done() }) @@ -220,6 +227,7 @@ describe('Connection', () => { await expect(async () => { await s.disconnect() }).rejects.toThrow() + expect(onDone).toHaveBeenCalledTimes(0) expect(s.isDisconnected()).toBeFalsy() }) @@ -231,6 +239,7 @@ describe('Connection', () => { await s.connect() s.debug('connect done') expect(s.isConnected()).toBeTruthy() + expect(onDone).toHaveBeenCalledTimes(0) done() }) @@ -267,10 +276,12 @@ describe('Connection', () => { s.on('connected', onConnected) onError = jest.fn() s.on('error', onError) + s.on('done', onDone) await expect(async () => { await s.connect() }).rejects.toThrow('badurl') expect(onConnected).toHaveBeenCalledTimes(0) + expect(onDone).toHaveBeenCalledTimes(1) }) it('rejects if cannot connect', async () => { @@ -281,12 +292,14 @@ describe('Connection', () => { }) onConnected = jest.fn() s.on('connected', onConnected) + s.on('done', onDone) onError = jest.fn() s.on('error', onError) await expect(async () => { await s.connect() }).rejects.toThrow('Unexpected server response') expect(onConnected).toHaveBeenCalledTimes(0) + expect(onDone).toHaveBeenCalledTimes(1) }) it('disconnect does not error if never connected', async () => { @@ -303,6 +316,7 @@ describe('Connection', () => { await s.disconnect() expect(s.isDisconnected()).toBeTruthy() expect(onDisconnected).toHaveBeenCalledTimes(1) + expect(onDone).toHaveBeenCalledTimes(1) }) it('disconnect does not error if already closing', async () => { @@ -314,6 +328,7 @@ describe('Connection', () => { expect(s.isDisconnected()).toBeTruthy() expect(onConnected).toHaveBeenCalledTimes(1) expect(onDisconnected).toHaveBeenCalledTimes(1) + expect(onDone).toHaveBeenCalledTimes(1) }) it('can handle disconnect before connect complete', async () => { @@ -327,6 +342,7 @@ describe('Connection', () => { expect(s.isDisconnected()).toBeTruthy() expect(onConnected).toHaveBeenCalledTimes(0) expect(onDisconnected).toHaveBeenCalledTimes(0) + expect(onDone).toHaveBeenCalledTimes(1) }) it('can handle connect before disconnect complete', async () => { @@ -341,6 +357,7 @@ describe('Connection', () => { expect(s.isConnected()).toBeTruthy() expect(onConnected).toHaveBeenCalledTimes(2) expect(onDisconnected).toHaveBeenCalledTimes(1) + expect(onDone).toHaveBeenCalledTimes(0) }) it('emits error but does not disconnect if connect event handler fails', async (done) => { @@ -357,6 +374,7 @@ describe('Connection', () => { }) await s.connect() expect(s.isConnected()).toBeTruthy() + expect(onDone).toHaveBeenCalledTimes(0) }) }) @@ -470,6 +488,7 @@ describe('Connection', () => { expect(err).toBeTruthy() expect(onConnected).toHaveBeenCalledTimes(1) expect(s.isDisconnected()).toBeTruthy() + expect(onDone).toHaveBeenCalledTimes(1) done() }) s.socket.close() @@ -508,6 +527,7 @@ describe('Connection', () => { expect(err).toBeTruthy() // wait a moment for late errors setTimeout(() => { + expect(onDone).toHaveBeenCalledTimes(1) done() }, 100) }) @@ -541,6 +561,7 @@ describe('Connection', () => { s.connect() )).rejects.toThrow('badurl') await s.disconnect() // shouldn't throw + expect(onDone).toHaveBeenCalledTimes(1) expect(s.isDisconnected()).toBeTruthy() // ensure close await expect(async () => ( @@ -549,6 +570,7 @@ describe('Connection', () => { s.disconnect(), ]) )).rejects.toThrow('disconnected before connected') + expect(onDone).toHaveBeenCalledTimes(2) s.options.url = goodUrl await s.connect() expect(s.isConnected()).toBeTruthy() @@ -568,6 +590,7 @@ describe('Connection', () => { // ensure is disconnected, not reconnecting expect(s.isDisconnected()).toBeTruthy() expect(s.isReconnecting()).toBeFalsy() + expect(onDone).toHaveBeenCalledTimes(1) done() }, 10) }) @@ -575,27 +598,29 @@ describe('Connection', () => { s.socket.close() }) - it('stops reconnecting if disconnected while reconnecting, after some delay', async (done) => { - await s.connect() - const goodUrl = s.options.url - s.options.url = 'badurl' - // once disconnected due to error, actually close - s.once('disconnected', async () => { - // wait a moment - setTimeout(async () => { - // i.e. would reconnect if not closing - s.options.url = goodUrl - await s.disconnect() + it('stops reconnecting if disconnected while reconnecting, after some delay', (done) => { + s.connect().then(() => { + const goodUrl = s.options.url + s.options.url = 'badurl' + // once disconnected due to error, actually close + s.once('disconnected', async () => { + // wait a moment setTimeout(async () => { - // ensure is disconnected, not reconnecting - expect(s.isDisconnected()).toBeTruthy() - expect(s.isReconnecting()).toBeFalsy() - done() - }, 20) - }, 10) + // i.e. would reconnect if not closing + s.options.url = goodUrl + await s.disconnect() + setTimeout(async () => { + // ensure is disconnected, not reconnecting + expect(s.isDisconnected()).toBeTruthy() + expect(s.isReconnecting()).toBeFalsy() + expect(onDone).toHaveBeenCalledTimes(1) + done() + }, 20) + }, 10) + }) + // trigger reconnecting cycle + s.socket.close() }) - // trigger reconnecting cycle - s.socket.close() }) }) From 5efd1b1d01aff59e6988bac23ac052986b43886f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 16 Oct 2020 17:07:28 -0400 Subject: [PATCH 102/517] Handle resubscribe on unexpected disconnection. --- src/subscribe/index.js | 101 ++++++++++++++++++++++++++------ test/integration/Stream.test.js | 84 ++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 18 deletions(-) diff --git a/src/subscribe/index.js b/src/subscribe/index.js index 8b2c89ed0..79a6de1a0 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -1,4 +1,5 @@ import { PassThrough } from 'stream' +import Emitter from 'events' import pMemoize from 'p-memoize' import pLimit from 'p-limit' @@ -100,6 +101,18 @@ export function validateOptions(optionsOrStreamId) { streamPartition: 0, } } else if (typeof optionsOrStreamId === 'object') { + if (optionsOrStreamId.stream) { + return validateOptions(optionsOrStreamId.stream) + } + + if (optionsOrStreamId.id != null && optionsOrStreamId.streamId == null) { + optionsOrStreamId.streamId = optionsOrStreamId.id + } + + if (optionsOrStreamId.partition == null && optionsOrStreamId.streamPartition == null) { + optionsOrStreamId.streamPartition = optionsOrStreamId.partition + } + // shallow copy options = { streamPartition: 0, @@ -447,21 +460,49 @@ async function getResendStream(client, opts) { * When all iterators are done, calls unsubscribe. */ -class Subscription { +class Subscription extends Emitter { constructor(client, options) { + super() this.client = client this.options = validateOptions(options) this.streams = new Set() this.queue = pLimit(1) - const sub = this.subscribe.bind(this) + const sub = this._subscribe.bind(this) const unsub = this.unsubscribe.bind(this) - this.subscribe = () => this.queue(sub) + this._subscribe = () => this.queue(sub) this.unsubscribe = () => this.queue(unsub) this.return = this.return.bind(this) this.sendSubscribe = pMemoize(this.sendSubscribe.bind(this)) this.sendUnsubscribe = pMemoize(this.sendUnsubscribe.bind(this)) this.validate = Validator(client, options) + this.onConnected = this.onConnected.bind(this) + this.onDisconnected = this.onDisconnected.bind(this) + this.onDisconnecting = this.onDisconnecting.bind(this) + this.onConnectionDone = this.onConnectionDone.bind(this) + } + + async onConnected() { + try { + await this.sendSubscribe() + } catch (err) { + this.emit('error', err) + } + } + + async onDisconnected() { + // unblock subscribe + pMemoize.clear(this.sendSubscribe) + } + + async onDisconnecting() { + if (!this.client.connection.shouldConnect) { + this.cancel() + } + } + + onConnectionDone() { + this.cancel() } hasPending() { @@ -476,19 +517,49 @@ class Subscription { return unsubscribe(this.client, this.options) } - async subscribe() { + _cleanupConnectionHandlers() { + // noop will be replaced in subscribe + } + + async _subscribe() { pMemoize.clear(this.sendUnsubscribe) - const iterator = this.iterate() // start iterator immediately + const { connection } = this.client + this._cleanupConnectionHandlers = connection.onTransition({ + connection: this.client.connection, + onConnected: this.onConnected, + onDisconnected: this.onDisconnected, + onDisconnecting: this.onDisconnecting, + onDone: this.onConnectionDone, + }) await this.sendSubscribe() + } + + async subscribe() { + const iterator = this.iterate() // start iterator immediately + await this._subscribe() return iterator } - async _unsubscribe() { + async return() { + await allSettledValues([...this.streams].map(async (it) => { + await it.return() + }), 'return failed') + } + + async onSubscriptionDone() { pMemoize.clear(this.sendSubscribe) - await this.sendUnsubscribe() + this._cleanupConnectionHandlers() + if (this.client.connection.shouldConnect) { + await this.sendUnsubscribe() + } + } + + async unsubscribe(...args) { + return this.cancel(...args) } async cancel(optionalErr) { + this._cleanupConnectionHandlers() if (this.hasPending()) { await this.queue(() => {}) } @@ -497,23 +568,13 @@ class Subscription { )), 'cancel failed') } - async return() { - await allSettledValues([...this.streams].map(async (it) => { - await it.return() - }), 'return failed') - } - - async unsubscribe(...args) { - return this.cancel(...args) - } - async _cleanup(it) { // if iterator never started, finally block never called, thus need to manually clean it const hadStream = this.streams.has(it) this.streams.delete(it) if (hadStream && !this.streams.size) { // unsubscribe if no more streams - await this._unsubscribe() + await this.onSubscriptionDone() } } @@ -555,6 +616,10 @@ export default class Subscriptions { this.subscriptions = new Map() } + getAll() { + return [...this.subscriptions.values()] + } + get(options) { const key = SubKey(validateOptions(options)) return this.subscriptions.get(key) diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index 4125e9e06..61782fd8f 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -498,6 +498,90 @@ describe('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) }) }) + + describe('connection states', () => { + let sub + + beforeEach(async () => { + if (sub) { + await sub.cancel() + } + }) + + it('should reconnect subscriptions when connection disconnected before subscribed & reconnected', async () => { + await client.connect() + const subTask = M.subscribe(stream.id) + await true + client.connection.socket.close() + const published = await publishTestMessages(2) + sub = await subTask + const received = [] + for await (const msg of sub) { + received.push(msg.getParsedContent()) + if (received.length === published.length) { + expect(received).toEqual(published) + } + break + } + }) + + it('should re-subscribe when subscribed then reconnected', async () => { + await client.connect() + const sub = await M.subscribe(stream.id) + let published = await publishTestMessages(2) + let received = [] + for await (const msg of sub) { + received.push(msg.getParsedContent()) + if (received.length === 2) { + expect(received).toEqual(published) + client.connection.socket.close() + published.push(...(await publishTestMessages(2))) + } + if (received.length === 4) { + expect(received).toEqual(published) + break + } + } + }) + + it('should end when subscribed then disconnected', async () => { + await client.connect() + const sub = await M.subscribe(stream.id) + let published = await publishTestMessages(2) + let received = [] + for await (const msg of sub) { + received.push(msg.getParsedContent()) + if (received.length === 1) { + expect(received).toEqual(published.slice(0, 1)) + client.disconnect() // should trigger break + // no await, should be immediate + } + } + expect(received).toEqual(published.slice(0, 1)) + }) + + it('should end when subscribed then disconnected', async () => { + await client.connect() + const sub = await M.subscribe(stream.id) + let published = await publishTestMessages(2) + let received = [] + await client.disconnect() + for await (const msg of sub) { + received.push(msg.getParsedContent()) + } + client.connect() // no await, should be ok + const sub2 = await M.subscribe(stream.id) + let published2 = await publishTestMessages(2) + let received2 = [] + for await (const msg of sub2) { + received2.push(msg.getParsedContent()) + if (received2.length === 1) { + await client.disconnect() + } + } + expect(received2).toEqual(published2.slice(0, 1)) + }) + }) }) } }) From 0cda3e2bcb5a252f8e90fc3da47e54e433749ad7 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 23 Oct 2020 14:49:43 -0500 Subject: [PATCH 103/517] Move more shared test utils into test/utils. --- src/Connection.js | 2 +- test/integration/Stream.test.js | 897 ++++++++++++++------------------ test/utils.js | 51 ++ 3 files changed, 443 insertions(+), 507 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index 794e69511..71e4fb67d 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -467,8 +467,8 @@ export default class Connection extends EventEmitter { this.checkDone() } } - }) + this.disconnectTask = disconnectTask return this.disconnectTask } diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index 61782fd8f..eb640e8de 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -1,9 +1,9 @@ import { ControlLayer } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' -import { uid, fakePrivateKey } from '../utils' +import { uid, fakePrivateKey, describeRepeats, getPublishTestMessages, collect } from '../utils' import StreamrClient from '../../src' -import { Defer, pTimeout } from '../../src/utils' +import { Defer } from '../../src/utils' import Connection from '../../src/Connection' import MessageStream from '../../src/subscribe' @@ -11,577 +11,462 @@ import config from './config' const { ControlMessage } = ControlLayer -const Msg = (opts) => ({ - value: uid('msg'), - ...opts, -}) +const MAX_ITEMS = 2 -async function collect(iterator, fn = () => {}) { - const received = [] - for await (const msg of iterator) { - received.push(msg.getParsedContent()) - await fn({ - msg, iterator, received, +describeRepeats('StreamrClient Stream', () => { + let expectErrors = 0 // check no errors by default + let onError = jest.fn() + let client + let stream + let M + let publishTestMessages + + const createClient = (opts = {}) => { + const c = new StreamrClient({ + auth: { + privateKey: fakePrivateKey(), + }, + autoConnect: false, + autoDisconnect: false, + maxRetries: 2, + ...config.clientOptions, + ...opts, }) + c.onError = jest.fn() + c.on('error', onError) + return c } - return received -} - -const TEST_REPEATS = 3 -const MAX_ITEMS = 2 - -describe('StreamrClient Stream', () => { - for (let k = 0; k < TEST_REPEATS; k++) { - // eslint-disable-next-line no-loop-func - describe(`test repeat ${k + 1} of ${TEST_REPEATS}`, () => { - let expectErrors = 0 // check no errors by default - let onError = jest.fn() - let client - let stream - let M - - const createClient = (opts = {}) => { - const c = new StreamrClient({ - auth: { - privateKey: fakePrivateKey(), - }, - autoConnect: false, - autoDisconnect: false, - maxRetries: 2, - ...config.clientOptions, - ...opts, - }) - c.onError = jest.fn() - c.on('error', onError) - return c + beforeEach(async () => { + expectErrors = 0 + onError = jest.fn() + }) + + beforeEach(async () => { + // eslint-disable-next-line require-atomic-updates + client = createClient() + M = new MessageStream(client) + client.debug('connecting before test >>') + await Promise.all([ + client.connect(), + client.session.getSessionToken(), + ]) + stream = await client.createStream({ + name: uid('stream') + }) + client.debug('connecting before test <<') + publishTestMessages = getPublishTestMessages(client, stream.id) + }) + + afterEach(async () => { + await wait() + // ensure no unexpected errors + expect(onError).toHaveBeenCalledTimes(expectErrors) + if (client) { + expect(client.onError).toHaveBeenCalledTimes(expectErrors) + } + }) + + afterEach(async () => { + await wait() + if (client) { + client.debug('disconnecting after test >>') + await client.disconnect() + client.debug('disconnecting after test <<') + } + + const openSockets = Connection.getOpen() + if (openSockets !== 0) { + throw new Error(`sockets not closed: ${openSockets}`) + } + }) + + it('attaches listener at subscribe time', async () => { + const beforeCount = client.connection.listenerCount(ControlMessage.TYPES.BroadcastMessage) + const sub = await M.subscribe(stream.id) + const afterCount = client.connection.listenerCount(ControlMessage.TYPES.BroadcastMessage) + expect(afterCount).toBeGreaterThan(beforeCount) + expect(M.count(stream.id)).toBe(1) + await sub.return() + expect(M.count(stream.id)).toBe(0) + }) + + describe('basics', () => { + it('can subscribe to stream and get updates then auto unsubscribe', async () => { + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) + + const published = await publishTestMessages() + + const received = [] + for await (const m of sub) { + received.push(m) + if (received.length === published.length) { + return + } } + expect(received).toEqual(published) + expect(M.count(stream.id)).toBe(0) + }) - beforeEach(async () => { - expectErrors = 0 - onError = jest.fn() - }) + it('subscribes immediately', async () => { + const sub = await M.subscribe(stream.id) - beforeEach(async () => { - // eslint-disable-next-line require-atomic-updates - client = createClient() - M = new MessageStream(client) - client.debug('connecting before test >>') - await Promise.all([ - client.connect(), - client.session.getSessionToken(), - ]) - stream = await client.createStream({ - name: uid('stream') - }) - client.debug('connecting before test <<') - }) + expect(M.count(stream.id)).toBe(1) - afterEach(async () => { - await wait() - // ensure no unexpected errors - expect(onError).toHaveBeenCalledTimes(expectErrors) - if (client) { - expect(client.onError).toHaveBeenCalledTimes(expectErrors) - } - }) + const published = await publishTestMessages() - afterEach(async () => { - await wait() - if (client) { - client.debug('disconnecting after test >>') - await client.disconnect() - client.debug('disconnecting after test <<') + const received = [] + for await (const m of sub) { + received.push(m) + if (received.length === published.length) { + return } + } - const openSockets = Connection.getOpen() - if (openSockets !== 0) { - throw new Error(`sockets not closed: ${openSockets}`) - } - }) + expect(received).toEqual(published) + expect(M.count(stream.id)).toBe(0) + }) - async function publishTestMessages(n = 4, streamId = stream.id) { - const published = [] - for (let i = 0; i < n; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop, no-loop-func - await pTimeout(client.publish(streamId, message), 1500, `publish timeout ${streamId}: ${i} ${JSON.stringify(message)}`) - published.push(message) + it('can kill stream using async end', async () => { + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) + + await publishTestMessages() + let t + let expectedLength + const received = [] + try { + for await (const m of sub) { + received.push(m) + // after first message schedule end + if (received.length === 1) { + // eslint-disable-next-line no-loop-func + t = setTimeout(() => { + expectedLength = received.length + // should not see any more messages after end + sub.cancel() + }) + } } - return published + } finally { + clearTimeout(t) } + // gets some messages but not all + expect(received).toHaveLength(expectedLength) + expect(M.count(stream.id)).toBe(0) + }) - it('attaches listener at subscribe time', async () => { - const beforeCount = client.connection.listenerCount(ControlMessage.TYPES.BroadcastMessage) - const sub = await M.subscribe(stream.id) - const afterCount = client.connection.listenerCount(ControlMessage.TYPES.BroadcastMessage) - expect(afterCount).toBeGreaterThan(beforeCount) - expect(M.count(stream.id)).toBe(1) - await sub.return() - expect(M.count(stream.id)).toBe(0) + it('can kill stream with throw', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) }) + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) + + await publishTestMessages() + + const err = new Error('expected error') + const received = [] + await expect(async () => { + for await (const m of sub) { + received.push(m) + // after first message schedule end + if (received.length === 1) { + throw err + } + } + }).rejects.toThrow(err) + // gets some messages but not all + expect(received).toHaveLength(1) + expect(unsubscribeEvents).toHaveLength(1) + expect(M.count(stream.id)).toBe(0) + }) - describe('basics', () => { - it('can subscribe to stream and get updates then auto unsubscribe', async () => { - const sub = await M.subscribe(stream.id) - expect(M.count(stream.id)).toBe(1) + it('can subscribe to stream multiple times, get updates then unsubscribe', async () => { + const sub1 = await M.subscribe(stream.id) + const sub2 = await M.subscribe(stream.id) - const published = await publishTestMessages() + expect(M.count(stream.id)).toBe(2) - const received = [] - for await (const m of sub) { - received.push(m) - if (received.length === published.length) { - return - } + const published = await publishTestMessages() + + const [received1, received2] = await Promise.all([ + collect(sub1, async ({ received, iterator }) => { + if (received.length === published.length) { + await iterator.return() + } + }), + collect(sub2, async ({ received, iterator }) => { + if (received.length === published.length) { + await iterator.return() } - expect(received).toEqual(published) - expect(M.count(stream.id)).toBe(0) }) + ]) - it('subscribes immediately', async () => { - const sub = await M.subscribe(stream.id) + expect(received1).toEqual(published) + expect(received2).toEqual(received1) + expect(M.count(stream.id)).toBe(0) + }) - expect(M.count(stream.id)).toBe(1) + it('can subscribe to stream multiple times in parallel, get updates then unsubscribe', async () => { + const [sub1, sub2] = await Promise.all([ + M.subscribe(stream.id), + M.subscribe(stream.id), + ]) - const published = await publishTestMessages() + expect(M.count(stream.id)).toBe(2) + const published = await publishTestMessages() - const received = [] - for await (const m of sub) { - received.push(m) - if (received.length === published.length) { - return - } + const [received1, received2] = await Promise.all([ + collect(sub1, async ({ received, iterator }) => { + if (received.length === published.length) { + await iterator.return() } - - expect(received).toEqual(published) - expect(M.count(stream.id)).toBe(0) - }) - - it('can kill stream using async end', async () => { - const sub = await M.subscribe(stream.id) - expect(M.count(stream.id)).toBe(1) - - await publishTestMessages() - let t - let expectedLength - const received = [] - try { - for await (const m of sub) { - received.push(m) - // after first message schedule end - if (received.length === 1) { - // eslint-disable-next-line no-loop-func - t = setTimeout(() => { - expectedLength = received.length - // should not see any more messages after end - sub.cancel() - }) - } - } - } finally { - clearTimeout(t) + }), + collect(sub2, async ({ received, iterator }) => { + if (received.length === published.length) { + await iterator.return() } - // gets some messages but not all - expect(received).toHaveLength(expectedLength) - expect(M.count(stream.id)).toBe(0) - }) - - it('can kill stream with throw', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) - const sub = await M.subscribe(stream.id) - expect(M.count(stream.id)).toBe(1) - - await publishTestMessages() - - const err = new Error('expected error') - const received = [] - await expect(async () => { - for await (const m of sub) { - received.push(m) - // after first message schedule end - if (received.length === 1) { - throw err - } - } - }).rejects.toThrow(err) - // gets some messages but not all - expect(received).toHaveLength(1) - expect(unsubscribeEvents).toHaveLength(1) - expect(M.count(stream.id)).toBe(0) }) + ]) - it('can subscribe to stream multiple times, get updates then unsubscribe', async () => { - const sub1 = await M.subscribe(stream.id) - const sub2 = await M.subscribe(stream.id) - - expect(M.count(stream.id)).toBe(2) + expect(received1).toEqual(published) + expect(received2).toEqual(received1) + expect(M.count(stream.id)).toBe(0) + expect(M.get(stream.id)).toBeFalsey() + }) - const published = await publishTestMessages() + it('can subscribe to stream and get some updates then unsubscribe mid-stream with end', async () => { + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) - const [received1, received2] = await Promise.all([ - collect(sub1, async ({ received, iterator }) => { - if (received.length === published.length) { - await iterator.return() - } - }), - collect(sub2, async ({ received, iterator }) => { - if (received.length === published.length) { - await iterator.return() - } - }) - ]) + const published = await publishTestMessages() - expect(received1).toEqual(published) - expect(received2).toEqual(received1) - expect(M.count(stream.id)).toBe(0) - }) - - it('can subscribe to stream multiple times in parallel, get updates then unsubscribe', async () => { - const [sub1, sub2] = await Promise.all([ - M.subscribe(stream.id), - M.subscribe(stream.id), - ]) - - expect(M.count(stream.id)).toBe(2) - const published = await publishTestMessages() - - const [received1, received2] = await Promise.all([ - collect(sub1, async ({ received, iterator }) => { - if (received.length === published.length) { - await iterator.return() - } - }), - collect(sub2, async ({ received, iterator }) => { - if (received.length === published.length) { - await iterator.return() - } - }) - ]) + const received = [] + for await (const m of sub) { + received.push(m.getParsedContent()) + if (received.length === 1) { + await sub.cancel() + } + } - expect(received1).toEqual(published) - expect(received2).toEqual(received1) - expect(M.count(stream.id)).toBe(0) - }) + expect(received).toEqual(published.slice(0, 1)) + expect(M.count(stream.id)).toBe(0) + }) - it('can subscribe to stream and get some updates then unsubscribe mid-stream with end', async () => { - const sub = await M.subscribe(stream.id) - expect(M.count(stream.id)).toBe(1) + it('finishes unsubscribe before returning', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) - const published = await publishTestMessages() + const sub = await M.subscribe(stream.id) - const received = [] - for await (const m of sub) { - received.push(m.getParsedContent()) - if (received.length === 1) { - await sub.cancel() - } - } + const published = await publishTestMessages() - expect(received).toEqual(published.slice(0, 1)) + const received = [] + for await (const m of sub) { + received.push(m.getParsedContent()) + if (received.length === MAX_ITEMS) { + expect(unsubscribeEvents).toHaveLength(0) + await sub.return() + expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) - }) + } + } + expect(received).toHaveLength(MAX_ITEMS) + expect(received).toEqual(published.slice(0, MAX_ITEMS)) + expect(M.count(stream.id)).toBe(0) + }) - it('finishes unsubscribe before returning', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) - - const sub = await M.subscribe(stream.id) - - const published = await publishTestMessages() - - const received = [] - for await (const m of sub) { - received.push(m.getParsedContent()) - if (received.length === MAX_ITEMS) { - expect(unsubscribeEvents).toHaveLength(0) - await sub.return() - expect(unsubscribeEvents).toHaveLength(1) - expect(M.count(stream.id)).toBe(0) - } - } - expect(received).toHaveLength(MAX_ITEMS) - expect(received).toEqual(published.slice(0, MAX_ITEMS)) - expect(M.count(stream.id)).toBe(0) - }) + it('finishes unsubscribe before returning from cancel', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) - it('finishes unsubscribe before returning from cancel', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) - - const sub = await M.subscribe(stream.id) - - const published = await publishTestMessages() - - const received = [] - for await (const m of sub) { - received.push(m.getParsedContent()) - if (received.length === MAX_ITEMS) { - expect(unsubscribeEvents).toHaveLength(0) - await sub.cancel() - expect(unsubscribeEvents).toHaveLength(1) - expect(M.count(stream.id)).toBe(0) - } - } - expect(received).toHaveLength(MAX_ITEMS) - expect(received).toEqual(published.slice(0, MAX_ITEMS)) - expect(M.count(stream.id)).toBe(0) - }) + const sub = await M.subscribe(stream.id) - it('can cancel + return and it will wait for unsubscribe', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) - - const sub = await M.subscribe(stream.id) - - const published = await publishTestMessages() - - const received = [] - for await (const m of sub) { - received.push(m.getParsedContent()) - if (received.length === MAX_ITEMS) { - expect(unsubscribeEvents).toHaveLength(0) - const tasks = [ - sub.return(), - sub.cancel(), - ] - await Promise.race(tasks) - expect(unsubscribeEvents).toHaveLength(1) - await Promise.all(tasks) - expect(unsubscribeEvents).toHaveLength(1) - expect(M.count(stream.id)).toBe(0) - } - } - expect(received).toHaveLength(MAX_ITEMS) - expect(received).toEqual(published.slice(0, MAX_ITEMS)) - expect(M.count(stream.id)).toBe(0) - }) + const published = await publishTestMessages() - it('can cancel multiple times and it will wait for unsubscribe', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) - - const sub = await M.subscribe(stream.id) - - const published = await publishTestMessages() - - const received = [] - for await (const m of sub) { - received.push(m.getParsedContent()) - if (received.length === MAX_ITEMS) { - expect(unsubscribeEvents).toHaveLength(0) - const tasks = [ - sub.cancel(), - sub.cancel(), - sub.cancel(), - ] - await Promise.race(tasks) - expect(unsubscribeEvents).toHaveLength(1) - await Promise.all(tasks) - expect(unsubscribeEvents).toHaveLength(1) - expect(M.count(stream.id)).toBe(0) - } - } - expect(received).toHaveLength(MAX_ITEMS) - expect(received).toEqual(published.slice(0, MAX_ITEMS)) + const received = [] + for await (const m of sub) { + received.push(m.getParsedContent()) + if (received.length === MAX_ITEMS) { + expect(unsubscribeEvents).toHaveLength(0) + await sub.cancel() + expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) - }) + } + } + expect(received).toHaveLength(MAX_ITEMS) + expect(received).toEqual(published.slice(0, MAX_ITEMS)) + expect(M.count(stream.id)).toBe(0) + }) - it('will clean up if iterator returned before start', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) + it('can cancel + return and it will wait for unsubscribe', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) - const sub = await M.subscribe(stream.id) - expect(M.count(stream.id)).toBe(1) - await sub.return() - expect(M.count(stream.id)).toBe(0) + const sub = await M.subscribe(stream.id) - await publishTestMessages() + const published = await publishTestMessages() - const received = [] - for await (const m of sub) { - received.push(m.getParsedContent()) - } - expect(received).toHaveLength(0) + const received = [] + for await (const m of sub) { + received.push(m.getParsedContent()) + if (received.length === MAX_ITEMS) { + expect(unsubscribeEvents).toHaveLength(0) + const tasks = [ + sub.return(), + sub.cancel(), + ] + await Promise.race(tasks) + expect(unsubscribeEvents).toHaveLength(1) + await Promise.all(tasks) expect(unsubscribeEvents).toHaveLength(1) - expect(M.count(stream.id)).toBe(0) - }) + } + } + expect(received).toHaveLength(MAX_ITEMS) + expect(received).toEqual(published.slice(0, MAX_ITEMS)) + expect(M.count(stream.id)).toBe(0) + }) - it('can subscribe then unsubscribe in parallel', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) - const [sub] = await Promise.all([ - M.subscribe(stream.id), - M.unsubscribe(stream.id), - ]) - - await publishTestMessages() - - const received = [] - for await (const m of sub) { - received.push(m.getParsedContent()) - } + it('can cancel multiple times and it will wait for unsubscribe', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) - // shouldn't get any messages - expect(received).toHaveLength(0) + const sub = await M.subscribe(stream.id) + + const published = await publishTestMessages() + + const received = [] + for await (const m of sub) { + received.push(m.getParsedContent()) + if (received.length === MAX_ITEMS) { + expect(unsubscribeEvents).toHaveLength(0) + const tasks = [ + sub.cancel(), + sub.cancel(), + sub.cancel(), + ] + await Promise.race(tasks) + expect(unsubscribeEvents).toHaveLength(1) + await Promise.all(tasks) expect(unsubscribeEvents).toHaveLength(1) expect(M.count(stream.id)).toBe(0) - }) + } + } + expect(received).toHaveLength(MAX_ITEMS) + expect(received).toEqual(published.slice(0, MAX_ITEMS)) + expect(M.count(stream.id)).toBe(0) + }) + + it('will clean up if iterator returned before start', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) }) - describe('mid-stream stop methods', () => { - let sub1 - let sub2 - let published + const sub = await M.subscribe(stream.id) + expect(M.count(stream.id)).toBe(1) + await sub.return() + expect(M.count(stream.id)).toBe(0) - beforeEach(async () => { - sub1 = await M.subscribe(stream.id) - sub2 = await M.subscribe(stream.id) - published = await publishTestMessages(5) - }) + await publishTestMessages() - it('can subscribe to stream multiple times then unsubscribe all mid-stream', async () => { - let sub1Received - let sub1ReceivedAtUnsubscribe - const gotOne = Defer() - const [received1, received2] = await Promise.all([ - collect(sub1, async ({ received }) => { - sub1Received = received - gotOne.resolve() - }), - collect(sub2, async ({ received }) => { - await gotOne - if (received.length === MAX_ITEMS) { - sub1ReceivedAtUnsubscribe = sub1Received.slice() - await M.unsubscribe(stream.id) - } - }), - ]) - expect(received2).toEqual(published.slice(0, MAX_ITEMS)) - expect(received1).toEqual(published.slice(0, sub1ReceivedAtUnsubscribe.length)) - expect(sub1ReceivedAtUnsubscribe).toEqual(sub1Received) - expect(M.count(stream.id)).toBe(0) - }) + const received = [] + for await (const m of sub) { + received.push(m.getParsedContent()) + } + expect(received).toHaveLength(0) + expect(unsubscribeEvents).toHaveLength(1) - it('can subscribe to stream multiple times then return mid-stream', async () => { - const [received1, received2] = await Promise.all([ - collect(sub1, async ({ received, iterator }) => { - if (received.length === MAX_ITEMS - 1) { - await iterator.return() - } - }), - collect(sub2, async ({ received, iterator }) => { - if (received.length === MAX_ITEMS) { - await iterator.return() - } - }), - ]) - - expect(received1).toEqual(published.slice(0, MAX_ITEMS - 1)) - expect(received2).toEqual(published.slice(0, MAX_ITEMS)) - expect(M.count(stream.id)).toBe(0) - }) + expect(M.count(stream.id)).toBe(0) + }) + + it('can subscribe then unsubscribe in parallel', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) }) + const [sub] = await Promise.all([ + M.subscribe(stream.id), + M.unsubscribe(stream.id), + ]) - describe('connection states', () => { - let sub + await publishTestMessages() - beforeEach(async () => { - if (sub) { - await sub.cancel() - } - }) + const received = [] + for await (const m of sub) { + received.push(m.getParsedContent()) + } - it('should reconnect subscriptions when connection disconnected before subscribed & reconnected', async () => { - await client.connect() - const subTask = M.subscribe(stream.id) - await true - client.connection.socket.close() - const published = await publishTestMessages(2) - sub = await subTask - const received = [] - for await (const msg of sub) { - received.push(msg.getParsedContent()) - if (received.length === published.length) { - expect(received).toEqual(published) - } - break - } - }) + // shouldn't get any messages + expect(received).toHaveLength(0) + expect(unsubscribeEvents).toHaveLength(1) + expect(M.count(stream.id)).toBe(0) + }) + }) - it('should re-subscribe when subscribed then reconnected', async () => { - await client.connect() - const sub = await M.subscribe(stream.id) - let published = await publishTestMessages(2) - let received = [] - for await (const msg of sub) { - received.push(msg.getParsedContent()) - if (received.length === 2) { - expect(received).toEqual(published) - client.connection.socket.close() - published.push(...(await publishTestMessages(2))) - } - if (received.length === 4) { - expect(received).toEqual(published) - break - } - } - }) + describe('mid-stream stop methods', () => { + let sub1 + let sub2 + let published + + beforeEach(async () => { + sub1 = await M.subscribe(stream.id) + sub2 = await M.subscribe(stream.id) + published = await publishTestMessages(5) + }) - it('should end when subscribed then disconnected', async () => { - await client.connect() - const sub = await M.subscribe(stream.id) - let published = await publishTestMessages(2) - let received = [] - for await (const msg of sub) { - received.push(msg.getParsedContent()) - if (received.length === 1) { - expect(received).toEqual(published.slice(0, 1)) - client.disconnect() // should trigger break - // no await, should be immediate - } + it('can subscribe to stream multiple times then unsubscribe all mid-stream', async () => { + let sub1Received + let sub1ReceivedAtUnsubscribe + const gotOne = Defer() + const [received1, received2] = await Promise.all([ + collect(sub1, async ({ received }) => { + sub1Received = received + gotOne.resolve() + }), + collect(sub2, async ({ received }) => { + await gotOne + if (received.length === MAX_ITEMS) { + sub1ReceivedAtUnsubscribe = sub1Received.slice() + await M.unsubscribe(stream.id) } - expect(received).toEqual(published.slice(0, 1)) - }) + }), + ]) + expect(received2).toEqual(published.slice(0, MAX_ITEMS)) + expect(received1).toEqual(published.slice(0, sub1ReceivedAtUnsubscribe.length)) + expect(sub1ReceivedAtUnsubscribe).toEqual(sub1Received) + expect(M.count(stream.id)).toBe(0) + }) - it('should end when subscribed then disconnected', async () => { - await client.connect() - const sub = await M.subscribe(stream.id) - let published = await publishTestMessages(2) - let received = [] - await client.disconnect() - for await (const msg of sub) { - received.push(msg.getParsedContent()) + it('can subscribe to stream multiple times then return mid-stream', async () => { + const [received1, received2] = await Promise.all([ + collect(sub1, async ({ received, iterator }) => { + if (received.length === MAX_ITEMS - 1) { + await iterator.return() } - client.connect() // no await, should be ok - const sub2 = await M.subscribe(stream.id) - let published2 = await publishTestMessages(2) - let received2 = [] - for await (const msg of sub2) { - received2.push(msg.getParsedContent()) - if (received2.length === 1) { - await client.disconnect() - } + }), + collect(sub2, async ({ received, iterator }) => { + if (received.length === MAX_ITEMS) { + await iterator.return() } - expect(received2).toEqual(published2.slice(0, 1)) - }) - }) + }), + ]) + + expect(received1).toEqual(published.slice(0, MAX_ITEMS - 1)) + expect(received2).toEqual(published.slice(0, MAX_ITEMS)) + expect(M.count(stream.id)).toBe(0) }) - } + }) }) diff --git a/test/utils.js b/test/utils.js index 524b90c3d..170c60637 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,3 +1,5 @@ +import { pTimeout } from '../src/utils' + const crypto = require('crypto') const uniqueId = require('lodash.uniqueid') @@ -7,3 +9,52 @@ export const uid = (prefix) => uniqueId(`p${process.pid}${prefix ? '-' + prefix export function fakePrivateKey() { return crypto.randomBytes(32).toString('hex') } + +const TEST_REPEATS = parseInt(process.env.TEST_REPEATS, 10) || 1 + +export function describeRepeats(msg, fn, describeFn = describe) { + for (let k = 0; k < TEST_REPEATS; k++) { + // eslint-disable-next-line no-loop-func + describe(msg, () => { + describeFn(`test repeat ${k + 1} of ${TEST_REPEATS}`, fn) + }) + } +} + +describeRepeats.skip = (msg, fn) => { + describe.skip(`test repeat ALL of ${TEST_REPEATS}`, fn) +} + +describeRepeats.only = (msg, fn) => { + describeRepeats(fn, describe.only) +} + +export async function collect(iterator, fn = () => {}) { + const received = [] + for await (const msg of iterator) { + received.push(msg.getParsedContent()) + await fn({ + msg, iterator, received, + }) + } + + return received +} + +export const Msg = (opts) => ({ + value: uid('msg'), + ...opts, +}) + +export function getPublishTestMessages(client, defaultStreamId) { + return async (n = 4, streamId = defaultStreamId) => { + const published = [] + for (let i = 0; i < n; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop, no-loop-func + await pTimeout(client.publish(streamId, message), 1500, `publish timeout ${streamId}: ${i} ${JSON.stringify(message)}`) + published.push(message) + } + return published + } +} From 3370f91c663181b95c6051c917aae22cb83ac7bc Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 23 Oct 2020 14:50:32 -0500 Subject: [PATCH 104/517] Add connection state integration tests. --- .../integration/StreamConnectionState.test.js | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 test/integration/StreamConnectionState.test.js diff --git a/test/integration/StreamConnectionState.test.js b/test/integration/StreamConnectionState.test.js new file mode 100644 index 000000000..04cb302c7 --- /dev/null +++ b/test/integration/StreamConnectionState.test.js @@ -0,0 +1,192 @@ +import { wait } from 'streamr-test-utils' + +import { uid, fakePrivateKey, describeRepeats, getPublishTestMessages } from '../utils' +import StreamrClient from '../../src' +import Connection from '../../src/Connection' +import MessageStream from '../../src/subscribe' + +import config from './config' + +describeRepeats('Connection State', () => { + let expectErrors = 0 // check no errors by default + let publishTestMessages + let onError = jest.fn() + let client + let stream + let M + + const createClient = (opts = {}) => { + const c = new StreamrClient({ + auth: { + privateKey: fakePrivateKey(), + }, + autoConnect: false, + autoDisconnect: false, + maxRetries: 2, + ...config.clientOptions, + ...opts, + }) + c.onError = jest.fn() + c.on('error', onError) + return c + } + + beforeEach(async () => { + expectErrors = 0 + onError = jest.fn() + }) + + beforeEach(async () => { + // eslint-disable-next-line require-atomic-updates + client = createClient() + M = new MessageStream(client) + client.debug('connecting before test >>') + await Promise.all([ + client.connect(), + client.session.getSessionToken(), + ]) + stream = await client.createStream({ + name: uid('stream') + }) + + client.debug('connecting before test <<') + publishTestMessages = getPublishTestMessages(client, stream.id) + }) + + afterEach(async () => { + await wait() + // ensure no unexpected errors + expect(onError).toHaveBeenCalledTimes(expectErrors) + if (client) { + expect(client.onError).toHaveBeenCalledTimes(expectErrors) + } + }) + + afterEach(async () => { + await wait() + if (client) { + client.debug('disconnecting after test >>') + await client.disconnect() + client.debug('disconnecting after test <<') + } + + const openSockets = Connection.getOpen() + if (openSockets !== 0) { + throw new Error(`sockets not closed: ${openSockets}`) + } + }) + + let subs = [] + + beforeEach(async () => { + const existingSubs = subs + subs = [] + await Promise.all(existingSubs.map((sub) => ( + sub.cancel() + ))) + }) + + it('should reconnect subscriptions when connection disconnected before subscribed & reconnected', async () => { + await client.connect() + const subTask = M.subscribe(stream.id) + await true + client.connection.socket.close() + const published = await publishTestMessages(2) + const sub = await subTask + subs.push(sub) + const received = [] + for await (const msg of sub) { + received.push(msg.getParsedContent()) + if (received.length === published.length) { + expect(received).toEqual(published) + } + break + } + expect(M.count(stream.id)).toBe(0) + expect(client.getSubscriptions(stream.id)).toEqual([]) + }) + + it('should re-subscribe when subscribed then reconnected', async () => { + await client.connect() + const sub = await M.subscribe(stream.id) + subs.push(sub) + const published = await publishTestMessages(2) + const received = [] + for await (const msg of sub) { + received.push(msg.getParsedContent()) + if (received.length === 2) { + expect(received).toEqual(published) + client.connection.socket.close() + published.push(...(await publishTestMessages(2))) + } + + if (received.length === 4) { + expect(received).toEqual(published) + break + } + } + expect(M.count(stream.id)).toBe(0) + expect(client.getSubscriptions(stream.id)).toEqual([]) + }) + + it('should end when subscribed then disconnected', async () => { + await client.connect() + const sub = await M.subscribe(stream.id) + subs.push(sub) + const published = await publishTestMessages(2) + const received = [] + for await (const msg of sub) { + received.push(msg.getParsedContent()) + if (received.length === 1) { + expect(received).toEqual(published.slice(0, 1)) + client.disconnect() // should trigger break + // no await, should be immediate + } + } + expect(received).toEqual(published.slice(0, 1)) + expect(M.count(stream.id)).toBe(0) + expect(client.getSubscriptions(stream.id)).toEqual([]) + }) + + it('should end when subscribed then disconnected', async () => { + await client.connect() + const sub = await M.subscribe(stream.id) + expect(client.getSubscriptions(stream.id)).toHaveLength(1) + subs.push(sub) + + await publishTestMessages(2) + const received = [] + await client.disconnect() + expect(M.count(stream.id)).toBe(0) + expect(client.getSubscriptions(stream.id)).toHaveLength(0) + for await (const msg of sub) { + received.push(msg.getParsedContent()) + } + expect(received).toEqual([]) + client.connect() // no await, should be ok + const sub2 = await M.subscribe(stream.id) + subs.push(sub) + const published2 = await publishTestMessages(2) + const received2 = [] + expect(M.count(stream.id)).toBe(1) + expect(client.getSubscriptions(stream.id)).toHaveLength(1) + for await (const msg of sub2) { + received2.push(msg.getParsedContent()) + if (received2.length === 1) { + await client.disconnect() + } + } + expect(received2).toEqual(published2.slice(0, 1)) + expect(M.count(stream.id)).toBe(0) + expect(client.getSubscriptions(stream.id)).toEqual([]) + }) + + it('should just end subs when disconnected', async () => { + await client.connect() + const sub = await M.subscribe(stream.id) + subs.push(sub) + await client.disconnect() + expect(M.count(stream.id)).toBe(0) + expect(client.getSubscriptions(stream.id)).toEqual([]) + }) +}) From 52237deed7eb53e5072bcd31e0ccba8857eaef6b Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 23 Oct 2020 14:57:15 -0500 Subject: [PATCH 105/517] Support passing stream and {stream} to subscribe. --- src/subscribe/index.js | 9 +++--- test/integration/Stream.test.js | 50 +++++++++++++++++++++++++-------- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/subscribe/index.js b/src/subscribe/index.js index 79a6de1a0..1732f99ed 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -94,7 +94,7 @@ export function validateOptions(optionsOrStreamId) { } // Backwards compatibility for giving a streamId as first argument - let options + let options = {} if (typeof optionsOrStreamId === 'string') { options = { streamId: optionsOrStreamId, @@ -106,23 +106,24 @@ export function validateOptions(optionsOrStreamId) { } if (optionsOrStreamId.id != null && optionsOrStreamId.streamId == null) { - optionsOrStreamId.streamId = optionsOrStreamId.id + options.streamId = optionsOrStreamId.id } if (optionsOrStreamId.partition == null && optionsOrStreamId.streamPartition == null) { - optionsOrStreamId.streamPartition = optionsOrStreamId.partition + options.streamPartition = optionsOrStreamId.partition } // shallow copy options = { streamPartition: 0, + ...options, ...optionsOrStreamId } } else { throw new Error(`options must be an object! Given: ${optionsOrStreamId}`) } - if (!options.streamId) { + if (options.streamId == null) { throw new Error(`streamId must be set, given: ${optionsOrStreamId}`) } diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index eb640e8de..00aa382a5 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -58,6 +58,10 @@ describeRepeats('StreamrClient Stream', () => { publishTestMessages = getPublishTestMessages(client, stream.id) }) + afterEach(() => { + expect(M.count(stream.id)).toBe(0) + }) + afterEach(async () => { await wait() // ensure no unexpected errors @@ -88,10 +92,43 @@ describeRepeats('StreamrClient Stream', () => { expect(afterCount).toBeGreaterThan(beforeCount) expect(M.count(stream.id)).toBe(1) await sub.return() - expect(M.count(stream.id)).toBe(0) }) describe('basics', () => { + it('works when passing stream', async () => { + const sub = await M.subscribe(stream) + expect(M.count(stream.id)).toBe(1) + + const published = await publishTestMessages() + + const received = [] + for await (const m of sub) { + received.push(m) + if (received.length === published.length) { + return + } + } + expect(received).toEqual(published) + }) + + it('works when passing { stream: stream }', async () => { + const sub = await M.subscribe({ + stream, + }) + expect(M.count(stream.id)).toBe(1) + + const published = await publishTestMessages() + + const received = [] + for await (const m of sub) { + received.push(m) + if (received.length === published.length) { + return + } + } + expect(received).toEqual(published) + }) + it('can subscribe to stream and get updates then auto unsubscribe', async () => { const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) @@ -106,7 +143,6 @@ describeRepeats('StreamrClient Stream', () => { } } expect(received).toEqual(published) - expect(M.count(stream.id)).toBe(0) }) it('subscribes immediately', async () => { @@ -125,7 +161,6 @@ describeRepeats('StreamrClient Stream', () => { } expect(received).toEqual(published) - expect(M.count(stream.id)).toBe(0) }) it('can kill stream using async end', async () => { @@ -154,7 +189,6 @@ describeRepeats('StreamrClient Stream', () => { } // gets some messages but not all expect(received).toHaveLength(expectedLength) - expect(M.count(stream.id)).toBe(0) }) it('can kill stream with throw', async () => { @@ -181,7 +215,6 @@ describeRepeats('StreamrClient Stream', () => { // gets some messages but not all expect(received).toHaveLength(1) expect(unsubscribeEvents).toHaveLength(1) - expect(M.count(stream.id)).toBe(0) }) it('can subscribe to stream multiple times, get updates then unsubscribe', async () => { @@ -207,7 +240,6 @@ describeRepeats('StreamrClient Stream', () => { expect(received1).toEqual(published) expect(received2).toEqual(received1) - expect(M.count(stream.id)).toBe(0) }) it('can subscribe to stream multiple times in parallel, get updates then unsubscribe', async () => { @@ -234,8 +266,6 @@ describeRepeats('StreamrClient Stream', () => { expect(received1).toEqual(published) expect(received2).toEqual(received1) - expect(M.count(stream.id)).toBe(0) - expect(M.get(stream.id)).toBeFalsey() }) it('can subscribe to stream and get some updates then unsubscribe mid-stream with end', async () => { @@ -278,7 +308,6 @@ describeRepeats('StreamrClient Stream', () => { } expect(received).toHaveLength(MAX_ITEMS) expect(received).toEqual(published.slice(0, MAX_ITEMS)) - expect(M.count(stream.id)).toBe(0) }) it('finishes unsubscribe before returning from cancel', async () => { @@ -303,7 +332,6 @@ describeRepeats('StreamrClient Stream', () => { } expect(received).toHaveLength(MAX_ITEMS) expect(received).toEqual(published.slice(0, MAX_ITEMS)) - expect(M.count(stream.id)).toBe(0) }) it('can cancel + return and it will wait for unsubscribe', async () => { @@ -334,7 +362,6 @@ describeRepeats('StreamrClient Stream', () => { } expect(received).toHaveLength(MAX_ITEMS) expect(received).toEqual(published.slice(0, MAX_ITEMS)) - expect(M.count(stream.id)).toBe(0) }) it('can cancel multiple times and it will wait for unsubscribe', async () => { @@ -366,7 +393,6 @@ describeRepeats('StreamrClient Stream', () => { } expect(received).toHaveLength(MAX_ITEMS) expect(received).toEqual(published.slice(0, MAX_ITEMS)) - expect(M.count(stream.id)).toBe(0) }) it('will clean up if iterator returned before start', async () => { From 99da5faebf8ca680e3865d8b3b6f230c5df38183 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 23 Oct 2020 15:17:56 -0500 Subject: [PATCH 106/517] Clean up subscription object from collection when done. --- src/Connection.js | 2 +- src/StreamrClient.js | 6 ++--- src/subscribe/index.js | 23 +++++++++++++------ .../integration/StreamConnectionState.test.js | 11 ++++++--- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index 71e4fb67d..03885b33a 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -644,7 +644,7 @@ export default class Connection extends EventEmitter { onError, }) { let onDoneHandler - const cleanUp = async (...args) => { + const cleanUp = async () => { this .off('connecting', onConnecting) .off('connected', onConnected) diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 815565951..c14717c2c 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -235,9 +235,9 @@ export default class StreamrClient extends EventEmitter { //return this.subscriber.unsubscribeAll(...args) //} - //getSubscriptions(...args) { - //return this.subscriber.getSubscriptions(...args) - //} + getSubscriptions(...args) { + return this.messageStream.getAll(...args) + } async ensureConnected() { return this.connect() diff --git a/src/subscribe/index.js b/src/subscribe/index.js index 1732f99ed..8c841c171 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -462,9 +462,10 @@ async function getResendStream(client, opts) { */ class Subscription extends Emitter { - constructor(client, options) { + constructor(client, options, onFinal) { super() this.client = client + this.onFinal = onFinal this.options = validateOptions(options) this.streams = new Set() @@ -569,13 +570,18 @@ class Subscription extends Emitter { )), 'cancel failed') } + async _cleanupFinal() { + this.onFinal() + // unsubscribe if no more streams + await this.onSubscriptionDone() + } + async _cleanup(it) { // if iterator never started, finally block never called, thus need to manually clean it const hadStream = this.streams.has(it) this.streams.delete(it) if (hadStream && !this.streams.size) { - // unsubscribe if no more streams - await this.onSubscriptionDone() + await this._cleanupFinal() } } @@ -643,10 +649,13 @@ export default class Subscriptions { async subscribe(options) { const key = SubKey(validateOptions(options)) - const sub = ( - this.subscriptions.get(key) - || this.subscriptions.set(key, new Subscription(this.client, options)).get(key) - ) + let sub = this.subscriptions.get(key) + if (!sub) { + sub = new Subscription(this.client, options, () => { + this.subscriptions.delete(key, sub) + }) + this.subscriptions.set(key, sub) + } return sub.subscribe() } diff --git a/test/integration/StreamConnectionState.test.js b/test/integration/StreamConnectionState.test.js index 04cb302c7..3873d61c4 100644 --- a/test/integration/StreamConnectionState.test.js +++ b/test/integration/StreamConnectionState.test.js @@ -39,7 +39,7 @@ describeRepeats('Connection State', () => { beforeEach(async () => { // eslint-disable-next-line require-atomic-updates client = createClient() - M = new MessageStream(client) + M = client.messageStream client.debug('connecting before test >>') await Promise.all([ client.connect(), @@ -53,6 +53,11 @@ describeRepeats('Connection State', () => { publishTestMessages = getPublishTestMessages(client, stream.id) }) + afterEach(() => { + expect(M.count(stream.id)).toBe(0) + expect(client.getSubscriptions(stream.id)).toEqual([]) + }) + afterEach(async () => { await wait() // ensure no unexpected errors @@ -93,6 +98,7 @@ describeRepeats('Connection State', () => { client.connection.socket.close() const published = await publishTestMessages(2) const sub = await subTask + expect(client.getSubscriptions(stream.id)).toHaveLength(1) subs.push(sub) const received = [] for await (const msg of sub) { @@ -148,7 +154,7 @@ describeRepeats('Connection State', () => { expect(client.getSubscriptions(stream.id)).toEqual([]) }) - it('should end when subscribed then disconnected', async () => { + it('should end when subscribed then disconnected then connected', async () => { await client.connect() const sub = await M.subscribe(stream.id) expect(client.getSubscriptions(stream.id)).toHaveLength(1) @@ -187,6 +193,5 @@ describeRepeats('Connection State', () => { subs.push(sub) await client.disconnect() expect(M.count(stream.id)).toBe(0) - expect(client.getSubscriptions(stream.id)).toEqual([]) }) }) From 86c93a13a7f5f23b3b87278c6ebbfc9fca0c418d Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 23 Oct 2020 15:49:10 -0500 Subject: [PATCH 107/517] Verify subscribe only sent once. --- test/integration/Stream.test.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index 00aa382a5..648e19c76 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -218,6 +218,7 @@ describeRepeats('StreamrClient Stream', () => { }) it('can subscribe to stream multiple times, get updates then unsubscribe', async () => { + const send = jest.spyOn(M.client.connection, 'send') const sub1 = await M.subscribe(stream.id) const sub2 = await M.subscribe(stream.id) @@ -240,9 +241,15 @@ describeRepeats('StreamrClient Stream', () => { expect(received1).toEqual(published) expect(received2).toEqual(received1) + + // only subscribed once + expect(send.mock.calls.filter(([msg]) => ( + msg.type === ControlMessage.TYPES.SubscribeRequest + ))).toHaveLength(1) }) it('can subscribe to stream multiple times in parallel, get updates then unsubscribe', async () => { + const send = jest.spyOn(M.client.connection, 'send') const [sub1, sub2] = await Promise.all([ M.subscribe(stream.id), M.subscribe(stream.id), @@ -266,6 +273,11 @@ describeRepeats('StreamrClient Stream', () => { expect(received1).toEqual(published) expect(received2).toEqual(received1) + + // only subscribed once + expect(send.mock.calls.filter(([msg]) => ( + msg.type === ControlMessage.TYPES.SubscribeRequest + ))).toHaveLength(1) }) it('can subscribe to stream and get some updates then unsubscribe mid-stream with end', async () => { From 7dcc95b9fe8c0551329ae5ff1e36d7280bcccce1 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 26 Oct 2020 14:38:40 -0600 Subject: [PATCH 108/517] Reorganise Connection. --- src/Connection.js | 354 +++++++++++++++++++++++++--------------------- 1 file changed, 189 insertions(+), 165 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index 03885b33a..2b2fe79c0 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -124,25 +124,6 @@ export default class Connection extends EventEmitter { this.onDisconnectError = this.onDisconnectError.bind(this) } - async backoffWait() { - const { retryBackoffFactor = 1.2, maxRetryWait = 10000 } = this.options - return new Promise((resolve) => { - clearTimeout(this._backoffTimeout) - const timeout = Math.min( - maxRetryWait, // max wait time - Math.round((this.retryCount * 10) ** retryBackoffFactor) - ) - if (!timeout) { - this.debug({ - retryCount: this.retryCount, - options: this.options, - }) - } - this.debug('waiting %n', timeout) - this._backoffTimeout = setTimeout(resolve, timeout) - }) - } - emit(event, ...args) { if (event === 'error') { let [err] = args @@ -184,94 +165,6 @@ export default class Connection extends EventEmitter { return result } - async reconnect() { - const { maxRetries = DEFAULT_MAX_RETRIES } = this.options - if (this.reconnectTask) { - return this.reconnectTask - } - - const reconnectTask = (async () => { - if (this.retryCount > maxRetries) { - // no more retries - this._isReconnecting = false - return Promise.resolve() - } - - // closed, noop - if (!this.shouldConnect) { - this._isReconnecting = false - return Promise.resolve() - } - - this._isReconnecting = true - this.debug('reconnect()') - // wait for a moment - await this.backoffWait() - - // re-check if closed or closing - if (!this.shouldConnect) { - this._isReconnecting = false - return Promise.resolve() - } - - if (this.isConnected()) { - this._isReconnecting = false - return Promise.resolve() - } - - const { retryCount } = this - // try again - this.debug('attempting to reconnect %s of %s', retryCount, maxRetries) - this.emitTransition('reconnecting') - return this._connectOnce().then((value) => { - this.debug('reconnect %s of %s successful', retryCount, maxRetries) - // reset retry state - this.reconnectTask = undefined - this._isReconnecting = false - this.retryCount = 1 - return value - }, (err) => { - this.debug('attempt to reconnect %s of %s failed', retryCount, maxRetries, err) - this.debug = this._debug - this.reconnectTask = undefined - this.retryCount += 1 - if (this.retryCount > maxRetries) { - this.debug('no more retries') - // no more retries - this._isReconnecting = false - throw err - } - this.debug('trying again...') - return this.reconnect() - }) - })().finally(() => { - if (this.reconnectTask === reconnectTask) { - this._isReconnecting = false - this.reconnectTask = undefined - } - }) - this.reconnectTask = reconnectTask - return this.reconnectTask - } - - onConnectError(error) { - const err = new ConnectionError(error) - this.checkDone() - if (!this._isReconnecting) { - this.emit('error', err) - } - - throw err - } - - onDisconnectError(error) { - const err = new ConnectionError(error) - // no check for reconnecting - this.emit('error', err) - - throw err - } - checkDone() { if (!this.isDone && !this._isReconnecting && !this.disconnectTask) { this.isDone = true @@ -280,12 +173,18 @@ export default class Connection extends EventEmitter { } } + /** + * Connection + */ + async connect() { + this.shouldConnect = true + this.isDone = false + if (this.initialConnectTask) { return this.initialConnectTask } - this.shouldConnect = true - this.isDone = false + const initialConnectTask = this._connectOnce() .catch((err) => { if (this.initialConnectTask === initialConnectTask) { @@ -423,49 +322,101 @@ export default class Connection extends EventEmitter { this.emitTransition('connected') } - async nextDisconnection() { - if (this.isDisconnected()) { + async nextConnection() { + if (this.isConnected()) { return Promise.resolve() } - if (this.disconnectTask) { - return this.disconnectTask + if (this.initialConnectTask) { + return this.initialConnectTask } return new Promise((resolve, reject) => { let onError - const onDisconnected = () => { + const onConnected = () => { this.off('error', onError) resolve() } onError = (err) => { - this.off('disconnected', onDisconnected) + this.off('connected', onConnected) reject(err) } - this.once('disconnected', onDisconnected) + this.once('connected', onConnected) this.once('error', onError) }) } + async triggerConnectionOrWait() { + return Promise.all([ + this.nextConnection(), + this.maybeConnect() + ]) + } + + async maybeConnect() { + if (this.options.autoConnect || this.shouldConnect) { + // should be open, so wait for open or trigger new open + await this.connect() + } + } + + async needsConnection(msg) { + await this.maybeConnect() + if (!this.isConnected()) { + const { autoConnect } = this.options + let autoConnectMsg = `autoConnect is ${autoConnect}.` + if (this.didDisableAutoConnectAfterDisconnect) { + autoConnectMsg += ' Disabled automatically after explicit call to disconnect().' + } + // note we can't just let socket.send fail, + // have to do this check ourselves because the error appears + // to be uncatchable in the browser + throw new ConnectionError(`needs connection but connection ${this.getState()} and ${autoConnectMsg}.\n${msg}`) + } + } + + onConnectError(error) { + const err = new ConnectionError(error) + this.checkDone() + if (!this._isReconnecting) { + this.emit('error', err) + } + + throw err + } + + onDisconnectError(error) { + const err = new ConnectionError(error) + // no check for reconnecting + this.emit('error', err) + + throw err + } + + /** + * Disconnection + */ + async disconnect() { + this.didDisableAutoConnectAfterDisconnect = !!this.options.autoConnect this.options.autoConnect = false // reset auto-connect on manual disconnect this.shouldConnect = false this._isReconnecting = false + if (this.disconnectTask) { return this.disconnectTask } + let hadError = false - const disconnectTask = this._disconnect() - .catch((err) => { + const disconnectTask = this.__disconnect() + .catch(async (err) => { hadError = true return this.onDisconnectError(err) }) .finally(() => { - if (this.disconnectTask === disconnectTask) { - this.disconnectTask = undefined - if (!hadError) { - this.checkDone() - } + this.disconnectTask = undefined + if (!hadError) { + this.checkDone() } }) @@ -473,7 +424,7 @@ export default class Connection extends EventEmitter { return this.disconnectTask } - async _disconnect() { + async __disconnect() { if (this.connectTask) { try { await this.connectTask @@ -501,72 +452,121 @@ export default class Connection extends EventEmitter { } } - async nextConnection() { - if (this.isConnected()) { + async nextDisconnection() { + if (this.isDisconnected()) { return Promise.resolve() } - if (this.initialConnectTask) { - return this.initialConnectTask + if (this.disconnectTask) { + return this.disconnectTask } return new Promise((resolve, reject) => { let onError - const onConnected = () => { + const onDisconnected = () => { this.off('error', onError) resolve() } onError = (err) => { - this.off('connected', onConnected) + this.off('disconnected', onDisconnected) reject(err) } - this.once('connected', onConnected) + this.once('disconnected', onDisconnected) this.once('error', onError) }) } - async triggerConnectionOrWait() { - return Promise.all([ - this.nextConnection(), - this.maybeConnect() - ]) - } + /** + * Reconnection + */ - async maybeConnect() { - if (this.options.autoConnect || this.shouldConnect) { - // should be open, so wait for open or trigger new open - await this.connect() + async reconnect() { + const { maxRetries = DEFAULT_MAX_RETRIES } = this.options + if (this.reconnectTask) { + return this.reconnectTask } - } - async needsConnection(msg) { - await this.maybeConnect() - if (!this.isConnected()) { - // note we can't just let socket.send fail, - // have to do this check ourselves because the error appears - // to be uncatchable in the browser - throw new ConnectionError(`needs connection but connection ${this.getState()}. ${msg}`) - } - } + const reconnectTask = (async () => { + if (this.retryCount > maxRetries) { + // no more retries + this._isReconnecting = false + return Promise.resolve() + } - getState() { - if (this.isConnected()) { - return 'connected' - } + // closed, noop + if (!this.shouldConnect) { + this._isReconnecting = false + return Promise.resolve() + } - if (this.isDisconnected()) { - return 'disconnected' - } + this._isReconnecting = true + this.debug('reconnect()') + // wait for a moment + await this.backoffWait() - if (this.isConnecting()) { - return 'connecting' - } + // re-check if closed or closing + if (!this.shouldConnect) { + this._isReconnecting = false + return Promise.resolve() + } - if (this.isDisconnecting()) { - return 'disconnecting' - } + if (this.isConnected()) { + this._isReconnecting = false + return Promise.resolve() + } - return 'unknown' + const { retryCount } = this + // try again + this.debug('attempting to reconnect %s of %s', retryCount, maxRetries) + this.emitTransition('reconnecting') + return this._connectOnce().then((value) => { + this.debug('reconnect %s of %s successful', retryCount, maxRetries) + // reset retry state + this.reconnectTask = undefined + this._isReconnecting = false + this.retryCount = 1 + return value + }, (err) => { + this.debug('attempt to reconnect %s of %s failed', retryCount, maxRetries, err) + this.debug = this._debug + this.reconnectTask = undefined + this.retryCount += 1 + if (this.retryCount > maxRetries) { + this.debug('no more retries') + // no more retries + this._isReconnecting = false + throw err + } + this.debug('trying again...') + return this.reconnect() + }) + })().finally(() => { + if (this.reconnectTask === reconnectTask) { + this._isReconnecting = false + this.reconnectTask = undefined + } + }) + this.reconnectTask = reconnectTask + return this.reconnectTask + } + + async backoffWait() { + const { retryBackoffFactor = 1.2, maxRetryWait = 10000 } = this.options + return new Promise((resolve) => { + clearTimeout(this._backoffTimeout) + const timeout = Math.min( + maxRetryWait, // max wait time + Math.round((this.retryCount * 10) ** retryBackoffFactor) + ) + if (!timeout) { + this.debug({ + retryCount: this.retryCount, + options: this.options, + }) + } + this.debug('waiting %n', timeout) + this._backoffTimeout = setTimeout(resolve, timeout) + }) } async send(msg) { @@ -600,6 +600,30 @@ export default class Connection extends EventEmitter { }) } + /** + * Status flags + */ + + getState() { + if (this.isConnected()) { + return 'connected' + } + + if (this.isDisconnected()) { + return 'disconnected' + } + + if (this.isConnecting()) { + return 'connecting' + } + + if (this.isDisconnecting()) { + return 'disconnecting' + } + + return 'unknown' + } + isReconnecting() { return this._isReconnecting } From 761d08215a64d1288cac55d3d24f47db8e67051f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 26 Oct 2020 13:40:45 -0600 Subject: [PATCH 109/517] Add failing auto-disconnect Connection tests. --- .../integration/StreamConnectionState.test.js | 237 ++++++++++-------- test/unit/Connection.test.js | 133 +++++++++- 2 files changed, 270 insertions(+), 100 deletions(-) diff --git a/test/integration/StreamConnectionState.test.js b/test/integration/StreamConnectionState.test.js index 3873d61c4..5e6cb4812 100644 --- a/test/integration/StreamConnectionState.test.js +++ b/test/integration/StreamConnectionState.test.js @@ -3,7 +3,6 @@ import { wait } from 'streamr-test-utils' import { uid, fakePrivateKey, describeRepeats, getPublishTestMessages } from '../utils' import StreamrClient from '../../src' import Connection from '../../src/Connection' -import MessageStream from '../../src/subscribe' import config from './config' @@ -31,14 +30,9 @@ describeRepeats('Connection State', () => { return c } - beforeEach(async () => { - expectErrors = 0 - onError = jest.fn() - }) - - beforeEach(async () => { + async function setupClient(opts) { // eslint-disable-next-line require-atomic-updates - client = createClient() + client = createClient(opts) M = client.messageStream client.debug('connecting before test >>') await Promise.all([ @@ -51,6 +45,12 @@ describeRepeats('Connection State', () => { client.debug('connecting before test <<') publishTestMessages = getPublishTestMessages(client, stream.id) + return client + } + + beforeEach(async () => { + expectErrors = 0 + onError = jest.fn() }) afterEach(() => { @@ -91,107 +91,148 @@ describeRepeats('Connection State', () => { ))) }) - it('should reconnect subscriptions when connection disconnected before subscribed & reconnected', async () => { - await client.connect() - const subTask = M.subscribe(stream.id) - await true - client.connection.socket.close() - const published = await publishTestMessages(2) - const sub = await subTask - expect(client.getSubscriptions(stream.id)).toHaveLength(1) - subs.push(sub) - const received = [] - for await (const msg of sub) { - received.push(msg.getParsedContent()) - if (received.length === published.length) { - expect(received).toEqual(published) + describe.only('autoConnect/Disconnect enabled', () => { + beforeEach(async () => { + await setupClient({ // new client with autoConnect + autoConnect: true, + autoDisconnect: true, + }) + // don't explicitly disconnect + }) + + it('should connect on subscribe', async () => { + const sub = await M.subscribe(stream.id) + const published = await publishTestMessages(2) + expect(client.getSubscriptions(stream.id)).toHaveLength(1) + subs.push(sub) + const received = [] + for await (const msg of sub) { + received.push(msg.getParsedContent()) + if (received.length === published.length) { + expect(received).toEqual(published) + } + break } - break - } - expect(M.count(stream.id)).toBe(0) - expect(client.getSubscriptions(stream.id)).toEqual([]) + expect(M.count(stream.id)).toBe(0) + expect(client.getSubscriptions(stream.id)).toEqual([]) + expect(client.isConnected()).toBeFalsy() + }) }) - it('should re-subscribe when subscribed then reconnected', async () => { - await client.connect() - const sub = await M.subscribe(stream.id) - subs.push(sub) - const published = await publishTestMessages(2) - const received = [] - for await (const msg of sub) { - received.push(msg.getParsedContent()) - if (received.length === 2) { - expect(received).toEqual(published) - client.connection.socket.close() - published.push(...(await publishTestMessages(2))) - } + describe('autoConnect disabled', () => { + beforeEach(async () => { + return setupClient({ + autoConnect: false, + }) + }) + + it('should error subscribe if client disconnected', async () => { + await client.disconnect() + await expect(async () => { + await M.subscribe(stream.id) + }).rejects.toThrow() + expect(M.count(stream.id)).toBe(0) + expect(client.getSubscriptions(stream.id)).toEqual([]) + }) - if (received.length === 4) { - expect(received).toEqual(published) + it('should reconnect subscriptions when connection disconnected before subscribed & reconnected', async () => { + const subTask = M.subscribe(stream.id) + await true + client.connection.socket.close() + const published = await publishTestMessages(2) + const sub = await subTask + expect(client.getSubscriptions(stream.id)).toHaveLength(1) + subs.push(sub) + const received = [] + for await (const msg of sub) { + received.push(msg.getParsedContent()) + if (received.length === published.length) { + expect(received).toEqual(published) + } break } - } - expect(M.count(stream.id)).toBe(0) - expect(client.getSubscriptions(stream.id)).toEqual([]) - }) + expect(M.count(stream.id)).toBe(0) + expect(client.getSubscriptions(stream.id)).toEqual([]) + }) - it('should end when subscribed then disconnected', async () => { - await client.connect() - const sub = await M.subscribe(stream.id) - subs.push(sub) - const published = await publishTestMessages(2) - const received = [] - for await (const msg of sub) { - received.push(msg.getParsedContent()) - if (received.length === 1) { - expect(received).toEqual(published.slice(0, 1)) - client.disconnect() // should trigger break - // no await, should be immediate + it('should re-subscribe when subscribed then reconnected', async () => { + const sub = await M.subscribe(stream.id) + subs.push(sub) + const published = await publishTestMessages(2) + const received = [] + for await (const msg of sub) { + received.push(msg.getParsedContent()) + if (received.length === 2) { + expect(received).toEqual(published) + client.connection.socket.close() + published.push(...(await publishTestMessages(2))) + } + + if (received.length === 4) { + expect(received).toEqual(published) + break + } } - } - expect(received).toEqual(published.slice(0, 1)) - expect(M.count(stream.id)).toBe(0) - expect(client.getSubscriptions(stream.id)).toEqual([]) - }) + expect(M.count(stream.id)).toBe(0) + expect(client.getSubscriptions(stream.id)).toEqual([]) + }) - it('should end when subscribed then disconnected then connected', async () => { - await client.connect() - const sub = await M.subscribe(stream.id) - expect(client.getSubscriptions(stream.id)).toHaveLength(1) - subs.push(sub) + it('should end when subscribed then disconnected', async () => { + const sub = await M.subscribe(stream.id) + subs.push(sub) + const published = await publishTestMessages(2) + const received = [] + for await (const msg of sub) { + received.push(msg.getParsedContent()) + if (received.length === 1) { + expect(received).toEqual(published.slice(0, 1)) + client.disconnect() // should trigger break + // no await, should be immediate + } + } + expect(received).toEqual(published.slice(0, 1)) + expect(M.count(stream.id)).toBe(0) + expect(client.getSubscriptions(stream.id)).toEqual([]) + }) - await publishTestMessages(2) - const received = [] - await client.disconnect() - expect(M.count(stream.id)).toBe(0) - expect(client.getSubscriptions(stream.id)).toHaveLength(0) - for await (const msg of sub) { - received.push(msg.getParsedContent()) - } - expect(received).toEqual([]) - client.connect() // no await, should be ok - const sub2 = await M.subscribe(stream.id) - subs.push(sub) - const published2 = await publishTestMessages(2) - const received2 = [] - expect(M.count(stream.id)).toBe(1) - expect(client.getSubscriptions(stream.id)).toHaveLength(1) - for await (const msg of sub2) { - received2.push(msg.getParsedContent()) - if (received2.length === 1) { - await client.disconnect() + it('should end when subscribed then disconnected then connected', async () => { + const sub = await M.subscribe(stream.id) + expect(client.getSubscriptions(stream.id)).toHaveLength(1) + subs.push(sub) + + await publishTestMessages(2) + const received = [] + await client.disconnect() + expect(M.count(stream.id)).toBe(0) + expect(client.getSubscriptions(stream.id)).toHaveLength(0) + for await (const msg of sub) { + received.push(msg.getParsedContent()) } - } - expect(received2).toEqual(published2.slice(0, 1)) - expect(M.count(stream.id)).toBe(0) - expect(client.getSubscriptions(stream.id)).toEqual([]) - }) + expect(received).toEqual([]) + client.connect() // no await, should be ok + const sub2 = await M.subscribe(stream.id) + subs.push(sub) + const published2 = await publishTestMessages(2) + const received2 = [] + expect(M.count(stream.id)).toBe(1) + expect(client.getSubscriptions(stream.id)).toHaveLength(1) + for await (const msg of sub2) { + received2.push(msg.getParsedContent()) + if (received2.length === 1) { + await client.disconnect() + } + } + expect(received2).toEqual(published2.slice(0, 1)) + expect(M.count(stream.id)).toBe(0) + expect(client.getSubscriptions(stream.id)).toEqual([]) + }) - it('should just end subs when disconnected', async () => { - await client.connect() - const sub = await M.subscribe(stream.id) - subs.push(sub) - await client.disconnect() - expect(M.count(stream.id)).toBe(0) + it('should just end subs when disconnected', async () => { + await client.connect() + const sub = await M.subscribe(stream.id) + subs.push(sub) + await client.disconnect() + expect(M.count(stream.id)).toBe(0) + }) }) }) diff --git a/test/unit/Connection.test.js b/test/unit/Connection.test.js index 94f8f3c90..ad50060f2 100644 --- a/test/unit/Connection.test.js +++ b/test/unit/Connection.test.js @@ -7,6 +7,8 @@ import Connection from '../../src/Connection' const debug = Debug('StreamrClient').extend('test') +console.log = Debug('Streamr:: CONSOLE ') + describe('Connection', () => { let s let onConnected @@ -712,9 +714,137 @@ describe('Connection', () => { }) }) + describe('autoDisconnect', () => { + it('auto-disconnects when all handles removed', async () => { + s.options.autoDisconnect = true + s.options.autoConnect = true + expect(s.isDisconnected()).toBeTruthy() + await s.addHandle(1) + await s.removeHandle(1) // noop + await s.connect() + // must have had handle previously to disconnect + await s.removeHandle(1) + expect(s.isConnected()).toBeTruthy() + await s.addHandle(1) + expect(s.isConnected()).toBeTruthy() + // can take multiple of the same handle (no error) + await s.addHandle(1) + expect(s.isConnected()).toBeTruthy() + await s.addHandle(2) + expect(s.isConnected()).toBeTruthy() + await s.removeHandle(2) + expect(s.isConnected()).toBeTruthy() + // once both 1 & 2 are removed, should disconnect + await s.removeHandle(1) + expect(s.isDisconnected()).toBeTruthy() + // can remove multiple of same handle (noop) + await s.removeHandle(1) + expect(s.isDisconnected()).toBeTruthy() + // should not try reconnect + await wait(1000) + expect(s.isDisconnected()).toBeTruthy() + // auto disconnect should not affect auto-connect + expect(s.options.autoConnect).toBeTruthy() + await s.send('test') // ok + expect(s.isConnected()).toBeTruthy() + }) + + it('handles concurrent call to removeHandle then connect', async () => { + s.options.autoDisconnect = true + s.options.autoConnect = true + await s.connect() + await s.addHandle(1) + await Promise.all([ + s.removeHandle(1), + s.connect(), + ]) + + expect(s.isConnected()).toBeTruthy() + }) + + it('handles concurrent call to connect then removeHandle', async () => { + s.options.autoDisconnect = true + s.options.autoConnect = true + await s.connect() + + expect(s.isConnected()).toBeTruthy() + await s.addHandle(1) + await Promise.all([ + s.connect(), + s.removeHandle(1), + ]) + expect(s.isDisconnected()).toBeTruthy() + expect(s.options.autoConnect).toBeTruthy() + }) + + it('handles concurrent call to disconnect then removeHandle', async () => { + s.options.autoDisconnect = true + s.options.autoConnect = true + await s.connect() + + expect(s.isConnected()).toBeTruthy() + await s.addHandle(1) + await Promise.all([ + s.disconnect(), + s.removeHandle(1), + ]) + expect(s.isDisconnected()).toBeTruthy() + expect(s.options.autoConnect).not.toBeTruthy() + }) + + it('handles concurrent call to removeHandle then disconnect', async () => { + s.options.autoDisconnect = true + s.options.autoConnect = true + await s.connect() + + expect(s.isConnected()).toBeTruthy() + await s.addHandle(1) + await Promise.all([ + s.disconnect(), + s.removeHandle(1), + ]) + expect(s.isDisconnected()).toBeTruthy() + expect(s.options.autoConnect).not.toBeTruthy() + }) + + it('handles concurrent call to removeHandle then disconnect + connect', async () => { + s.options.autoDisconnect = true + s.options.autoConnect = true + await s.connect() + expectErrors = 1 + expect(s.isConnected()).toBeTruthy() + await s.addHandle(1) + const tasks = Promise.all([ + expect(() => { + return s.disconnect() + }).rejects.toThrow(), + s.connect(), // this will cause disconnect call to throw + ]) + await s.removeHandle(1) + await tasks + expect(s.isConnected()).toBeTruthy() + expect(s.options.autoConnect).not.toBeTruthy() + }) + + it('does nothing if autoDisconnect is false', async () => { + s.options.autoDisconnect = false + s.options.autoConnect = false + await s.connect() + expect(s.isConnected()).toBeTruthy() + await s.addHandle(1) + expect(s.isConnected()).toBeTruthy() + await s.addHandle(2) + expect(s.isConnected()).toBeTruthy() + await s.removeHandle(2) + expect(s.isConnected()).toBeTruthy() + await s.removeHandle(1) + expect(s.isConnected()).toBeTruthy() + expect(s.options.autoConnect).not.toBeTruthy() + }) + }) + describe('onTransition', () => { it('runs functions', async () => { - const onError = jest.fn() const transitionFns = { onConnected: jest.fn(), onConnecting: jest.fn(), @@ -754,6 +884,5 @@ describe('Connection', () => { expect(transitionFns.onError).toHaveBeenCalledTimes(0) }) }) - }) From 26555b7b9f4df66518a7da57534da38c3936a5c2 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 26 Oct 2020 16:20:40 -0600 Subject: [PATCH 110/517] Convert connection tests to get state so more helpful when debugging. Fix getState() --- src/Connection.js | 9 ++- test/unit/Connection.test.js | 142 +++++++++++++++++------------------ 2 files changed, 73 insertions(+), 78 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index 2b2fe79c0..3364ddd5e 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -609,14 +609,15 @@ export default class Connection extends EventEmitter { return 'connected' } - if (this.isDisconnected()) { - return 'disconnected' - } - if (this.isConnecting()) { + // this check must go before isDisconnected return 'connecting' } + if (this.isDisconnected()) { + return 'disconnected' + } + if (this.isDisconnecting()) { return 'disconnecting' } diff --git a/test/unit/Connection.test.js b/test/unit/Connection.test.js index ad50060f2..f232c3156 100644 --- a/test/unit/Connection.test.js +++ b/test/unit/Connection.test.js @@ -66,19 +66,13 @@ describe('Connection', () => { describe('basics', () => { it('can connect & disconnect', async () => { const connectTask = s.connect() - expect(s.isConnecting()).toBeTruthy() + expect(s.getState()).toBe('connecting') await connectTask - expect(s.isDisconnected()).toBeFalsy() - expect(s.isDisconnecting()).toBeFalsy() - expect(s.isConnecting()).toBeFalsy() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') const disconnectTask = s.disconnect() - expect(s.isDisconnecting()).toBeTruthy() + expect(s.getState()).toBe('disconnecting') await disconnectTask - expect(s.isConnected()).toBeFalsy() - expect(s.isDisconnecting()).toBeFalsy() - expect(s.isConnecting()).toBeFalsy() - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') // check events expect(onConnected).toHaveBeenCalledTimes(1) expect(onDisconnected).toHaveBeenCalledTimes(1) @@ -87,7 +81,7 @@ describe('Connection', () => { it('can connect after already connected', async () => { await s.connect() await s.connect() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') expect(onConnected).toHaveBeenCalledTimes(1) expect(onConnecting).toHaveBeenCalledTimes(1) @@ -98,7 +92,7 @@ describe('Connection', () => { s.connect(), s.connect(), ]) - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') expect(onConnected).toHaveBeenCalledTimes(1) expect(onConnecting).toHaveBeenCalledTimes(1) }) @@ -108,12 +102,12 @@ describe('Connection', () => { s.connect(), s.connect(), ]) - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') await Promise.all([ s.disconnect(), s.disconnect(), ]) - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') expect(onConnected).toHaveBeenCalledTimes(1) expect(onDisconnected).toHaveBeenCalledTimes(1) @@ -129,7 +123,7 @@ describe('Connection', () => { s.socket.close() await s.nextConnection() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') expect(onConnected).toHaveBeenCalledTimes(2) expect(onDisconnected).toHaveBeenCalledTimes(1) @@ -139,12 +133,12 @@ describe('Connection', () => { it('can connect again after disconnect', async () => { await s.connect() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') const oldSocket = s.socket await s.disconnect() - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') await s.connect() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') // check events expect(onConnected).toHaveBeenCalledTimes(2) expect(onDisconnected).toHaveBeenCalledTimes(1) @@ -157,34 +151,34 @@ describe('Connection', () => { it('can handle connect on connecting event', async (done) => { s.once('connecting', async () => { await s.connect() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') expect(onConnected).toHaveBeenCalledTimes(1) expect(onConnecting).toHaveBeenCalledTimes(1) done() }) await s.connect() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') }) it('can handle disconnect on connecting event', async (done) => { expectErrors = 1 s.once('connecting', async () => { await s.disconnect() - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') expect(onDone).toHaveBeenCalledTimes(1) done() }) await expect(async () => { await s.connect() }).rejects.toThrow() - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') }) it('can handle disconnect on connected event', async (done) => { expectErrors = 1 s.once('connected', async () => { await s.disconnect() - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') expect(onDone).toHaveBeenCalledTimes(1) done() }) @@ -192,7 +186,7 @@ describe('Connection', () => { await expect(async () => { await s.connect() }).rejects.toThrow() - expect(s.isConnected()).toBeFalsy() + expect(s.getState()).not.toBe('connected') }) it('can handle disconnect on connected event, repeated', async (done) => { @@ -222,7 +216,7 @@ describe('Connection', () => { expectErrors = 1 s.once('disconnecting', async () => { await s.connect() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') done() }) await s.connect() @@ -230,7 +224,7 @@ describe('Connection', () => { await s.disconnect() }).rejects.toThrow() expect(onDone).toHaveBeenCalledTimes(0) - expect(s.isDisconnected()).toBeFalsy() + expect(s.getState()).not.toBe('disconnected') }) it('can handle connect on disconnected event', async (done) => { @@ -240,7 +234,7 @@ describe('Connection', () => { s.once('disconnected', async () => { await s.connect() s.debug('connect done') - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') expect(onDone).toHaveBeenCalledTimes(0) done() }) @@ -248,7 +242,7 @@ describe('Connection', () => { await expect(async () => { await s.disconnect() }).rejects.toThrow() - expect(s.isConnected()).toBeFalsy() + expect(s.getState()).not.toBe('connected') }) }) @@ -305,18 +299,18 @@ describe('Connection', () => { }) it('disconnect does not error if never connected', async () => { - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') await s.disconnect() - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') expect(onDisconnected).toHaveBeenCalledTimes(0) }) it('disconnect does not error if already disconnected', async () => { await s.connect() await s.disconnect() - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') await s.disconnect() - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') expect(onDisconnected).toHaveBeenCalledTimes(1) expect(onDone).toHaveBeenCalledTimes(1) }) @@ -327,7 +321,7 @@ describe('Connection', () => { s.disconnect(), s.disconnect(), ]) - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') expect(onConnected).toHaveBeenCalledTimes(1) expect(onDisconnected).toHaveBeenCalledTimes(1) expect(onDone).toHaveBeenCalledTimes(1) @@ -341,7 +335,7 @@ describe('Connection', () => { )).rejects.toThrow(), s.disconnect() ]) - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') expect(onConnected).toHaveBeenCalledTimes(0) expect(onDisconnected).toHaveBeenCalledTimes(0) expect(onDone).toHaveBeenCalledTimes(1) @@ -356,7 +350,7 @@ describe('Connection', () => { )).rejects.toThrow(), s.connect() ]) - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') expect(onConnected).toHaveBeenCalledTimes(2) expect(onDisconnected).toHaveBeenCalledTimes(1) expect(onDone).toHaveBeenCalledTimes(0) @@ -371,11 +365,11 @@ describe('Connection', () => { s.once('error', async (err) => { expect(err).toBe(error) await wait() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') done() }) await s.connect() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') expect(onDone).toHaveBeenCalledTimes(0) }) }) @@ -384,19 +378,19 @@ describe('Connection', () => { it('connects if no autoconnect', async () => { s.options.autoConnect = false const task = s.triggerConnectionOrWait() - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') await wait(20) await Promise.all([ task, s.connect() ]) - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') }) it('connects if autoconnect', async () => { s.options.autoConnect = true await s.triggerConnectionOrWait() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') }) it('errors if connect errors', async () => { @@ -406,7 +400,7 @@ describe('Connection', () => { await expect(async () => { await s.triggerConnectionOrWait() }).rejects.toThrow() - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') }) it('errors if connect errors without autoconnect', async () => { @@ -419,7 +413,7 @@ describe('Connection', () => { await s.connect() }).rejects.toThrow() await expect(task).rejects.toThrow() - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') }) }) @@ -476,7 +470,7 @@ describe('Connection', () => { it('reconnects if unexpectedly disconnected', async (done) => { await s.connect() s.once('connected', () => { - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') done() }) s.socket.close() @@ -489,7 +483,7 @@ describe('Connection', () => { s.once('error', async (err) => { expect(err).toBeTruthy() expect(onConnected).toHaveBeenCalledTimes(1) - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') expect(onDone).toHaveBeenCalledTimes(1) done() }) @@ -512,7 +506,7 @@ describe('Connection', () => { } }) s.once('connected', () => { - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') expect(retryCount).toEqual(3) done() }) @@ -548,7 +542,7 @@ describe('Connection', () => { await s.connect() setTimeout(() => { expect(s.isReconnecting()).toBeFalsy() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') done() }) }) @@ -564,7 +558,7 @@ describe('Connection', () => { )).rejects.toThrow('badurl') await s.disconnect() // shouldn't throw expect(onDone).toHaveBeenCalledTimes(1) - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') // ensure close await expect(async () => ( Promise.all([ @@ -575,7 +569,7 @@ describe('Connection', () => { expect(onDone).toHaveBeenCalledTimes(2) s.options.url = goodUrl await s.connect() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') }) it('stops reconnecting if disconnected while reconnecting', async (done) => { @@ -590,7 +584,7 @@ describe('Connection', () => { // wait a moment setTimeout(() => { // ensure is disconnected, not reconnecting - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') expect(s.isReconnecting()).toBeFalsy() expect(onDone).toHaveBeenCalledTimes(1) done() @@ -613,7 +607,7 @@ describe('Connection', () => { await s.disconnect() setTimeout(async () => { // ensure is disconnected, not reconnecting - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') expect(s.isReconnecting()).toBeFalsy() expect(onDone).toHaveBeenCalledTimes(1) done() @@ -718,35 +712,35 @@ describe('Connection', () => { it('auto-disconnects when all handles removed', async () => { s.options.autoDisconnect = true s.options.autoConnect = true - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') await s.addHandle(1) await s.removeHandle(1) // noop await s.connect() // must have had handle previously to disconnect await s.removeHandle(1) - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') await s.addHandle(1) - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') // can take multiple of the same handle (no error) await s.addHandle(1) - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') await s.addHandle(2) - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') await s.removeHandle(2) - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') // once both 1 & 2 are removed, should disconnect await s.removeHandle(1) - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') // can remove multiple of same handle (noop) await s.removeHandle(1) - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') // should not try reconnect await wait(1000) - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') // auto disconnect should not affect auto-connect expect(s.options.autoConnect).toBeTruthy() await s.send('test') // ok - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') }) it('handles concurrent call to removeHandle then connect', async () => { @@ -759,7 +753,7 @@ describe('Connection', () => { s.connect(), ]) - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') }) it('handles concurrent call to connect then removeHandle', async () => { @@ -767,13 +761,13 @@ describe('Connection', () => { s.options.autoConnect = true await s.connect() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') await s.addHandle(1) await Promise.all([ s.connect(), s.removeHandle(1), ]) - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') expect(s.options.autoConnect).toBeTruthy() }) @@ -782,13 +776,13 @@ describe('Connection', () => { s.options.autoConnect = true await s.connect() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') await s.addHandle(1) await Promise.all([ s.disconnect(), s.removeHandle(1), ]) - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') expect(s.options.autoConnect).not.toBeTruthy() }) @@ -797,13 +791,13 @@ describe('Connection', () => { s.options.autoConnect = true await s.connect() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') await s.addHandle(1) await Promise.all([ s.disconnect(), s.removeHandle(1), ]) - expect(s.isDisconnected()).toBeTruthy() + expect(s.getState()).toBe('disconnected') expect(s.options.autoConnect).not.toBeTruthy() }) @@ -812,7 +806,7 @@ describe('Connection', () => { s.options.autoConnect = true await s.connect() expectErrors = 1 - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') await s.addHandle(1) const tasks = Promise.all([ expect(() => { @@ -822,7 +816,7 @@ describe('Connection', () => { ]) await s.removeHandle(1) await tasks - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') expect(s.options.autoConnect).not.toBeTruthy() }) @@ -830,15 +824,15 @@ describe('Connection', () => { s.options.autoDisconnect = false s.options.autoConnect = false await s.connect() - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') await s.addHandle(1) - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') await s.addHandle(2) - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') await s.removeHandle(2) - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') await s.removeHandle(1) - expect(s.isConnected()).toBeTruthy() + expect(s.getState()).toBe('connected') expect(s.options.autoConnect).not.toBeTruthy() }) }) From af8b435b15c3809669d8440eafee73863027bc73 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 26 Oct 2020 16:21:47 -0600 Subject: [PATCH 111/517] Use local websocket server in connection unit tests. --- test/unit/Connection.test.js | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/test/unit/Connection.test.js b/test/unit/Connection.test.js index f232c3156..c48b13438 100644 --- a/test/unit/Connection.test.js +++ b/test/unit/Connection.test.js @@ -1,3 +1,4 @@ +import { Server } from 'ws' import { wait } from 'streamr-test-utils' import Debug from 'debug' @@ -19,12 +20,30 @@ describe('Connection', () => { let onDone let onError let onMessage + let wss + let port let expectErrors = 0 // check no errors by default + beforeAll((done) => { + wss = new Server({ + port: 0, + }).once('listening', () => { + port = wss.address().port + done() + }) + + wss.on('connection', (ws) => { + ws.on('message', (msg) => ws.send(msg)) + }) + }) + + afterAll((done) => { + wss.close(done) + }) beforeEach(() => { s = new Connection({ - url: 'wss://echo.websocket.org/', + url: `ws://localhost:${port}/`, maxRetries: 2 }) From e5bb67a8e54c3592de044f779a97708502421c43 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 26 Oct 2020 16:23:09 -0600 Subject: [PATCH 112/517] Implement auto-disconnect. --- src/Connection.js | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/Connection.js b/src/Connection.js index 3364ddd5e..895fbac75 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -122,6 +122,7 @@ export default class Connection extends EventEmitter { this.debug = this._debug this.onConnectError = this.onConnectError.bind(this) this.onDisconnectError = this.onDisconnectError.bind(this) + this.connectionHandles = new Set() } emit(event, ...args) { @@ -179,6 +180,7 @@ export default class Connection extends EventEmitter { async connect() { this.shouldConnect = true + this.shouldReconnect = true this.isDone = false if (this.initialConnectTask) { @@ -401,6 +403,7 @@ export default class Connection extends EventEmitter { this.didDisableAutoConnectAfterDisconnect = !!this.options.autoConnect this.options.autoConnect = false // reset auto-connect on manual disconnect this.shouldConnect = false + this.shouldReconnect = true this._isReconnecting = false if (this.disconnectTask) { @@ -486,6 +489,10 @@ export default class Connection extends EventEmitter { return this.reconnectTask } + if (!this.shouldReconnect) { + return Promise.resolve() + } + const reconnectTask = (async () => { if (this.retryCount > maxRetries) { // no more retries @@ -569,6 +576,37 @@ export default class Connection extends EventEmitter { }) } + /** + * Auto Connect/Disconnect counters. + */ + + async addHandle(id) { + this.connectionHandles.add(id) + } + + /** + * When no more handles and autoDisconnect is true, disconnect. + */ + + async removeHandle(id) { + const hadConnection = this.connectionHandles.has(id) + this.connectionHandles.delete(id) + const { socket } = this + if (hadConnection && this.connectionHandles.size === 0 && this.options.autoDisconnect) { + this.shouldReconnect = false + await this.__disconnect().catch(async (err) => { + if (err instanceof ConnectionError) { + if (!this.shouldReconnect) { + await CloseWebSocket(socket) + } + return + } + throw err + }) + this.shouldReconnect = false + } + } + async send(msg) { this.debug('send()') if (!this.isConnected()) { From d2f6b34877bf369e5764d4a9589bc8830a16c43f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 27 Oct 2020 12:26:48 -0600 Subject: [PATCH 113/517] Improve auto-disconnect. --- src/Connection.js | 61 +++++++++++++++++++++++++++++------- test/unit/Connection.test.js | 55 +++++++++++++++++++++++++++++--- 2 files changed, 100 insertions(+), 16 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index 895fbac75..813e04529 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -180,6 +180,10 @@ export default class Connection extends EventEmitter { async connect() { this.shouldConnect = true + if (this.connectedOnce) { + this.options.autoDisconnect = false // reset auto-connect on manual disconnect + } + this.connectedOnce = true this.shouldReconnect = true this.isDone = false @@ -402,6 +406,7 @@ export default class Connection extends EventEmitter { async disconnect() { this.didDisableAutoConnectAfterDisconnect = !!this.options.autoConnect this.options.autoConnect = false // reset auto-connect on manual disconnect + this.options.autoDisconnect = false // reset auto-disconnect on manual disconnect this.shouldConnect = false this.shouldReconnect = true this._isReconnecting = false @@ -581,6 +586,7 @@ export default class Connection extends EventEmitter { */ async addHandle(id) { + this.shouldReconnect = true this.connectionHandles.add(id) } @@ -591,20 +597,51 @@ export default class Connection extends EventEmitter { async removeHandle(id) { const hadConnection = this.connectionHandles.has(id) this.connectionHandles.delete(id) - const { socket } = this if (hadConnection && this.connectionHandles.size === 0 && this.options.autoDisconnect) { - this.shouldReconnect = false - await this.__disconnect().catch(async (err) => { - if (err instanceof ConnectionError) { - if (!this.shouldReconnect) { - await CloseWebSocket(socket) - } - return - } - throw err - }) - this.shouldReconnect = false + return this._autoDisconnect() } + + return Promise.resolve() + } + + async waitForPending() { + if (this.connectTask || this.disconnectTask) { + await Promise.all([ + Promise.resolve(this.connectTask).catch(() => {}), // ignore errors + Promise.resolve(this.disconnectTask).catch(() => {}), // ignore errors + ]) + + await true + + // wait for any additional queued tasks + return this.waitForPending() + } + return Promise.resolve() + } + + async _autoDisconnect() { + if (this.autoDisconnectTask) { + return this.autoDisconnectTask + } + + this.autoDisconnectTask = Promise.resolve().then(async () => { + this.shouldReconnect = false + await this.waitForPending() + // eslint-disable-next-line promise/always-return + if (this.connectionHandles.size === 0 && !this.shouldReconnect && this.options.autoDisconnect) { + await CloseWebSocket(this.socket) + } + }).catch(async (err) => { + if (err instanceof ConnectionError) { + // ignore ConnectionErrors because not user-initiated + return + } + throw err + }).finally(() => { + this.autoDisconnectTask = undefined + }) + + return this.autoDisconnectTask } async send(msg) { diff --git a/test/unit/Connection.test.js b/test/unit/Connection.test.js index c48b13438..8cecac72d 100644 --- a/test/unit/Connection.test.js +++ b/test/unit/Connection.test.js @@ -8,8 +8,6 @@ import Connection from '../../src/Connection' const debug = Debug('StreamrClient').extend('test') -console.log = Debug('Streamr:: CONSOLE ') - describe('Connection', () => { let s let onConnected @@ -70,7 +68,13 @@ describe('Connection', () => { afterEach(async () => { await wait() // ensure no unexpected errors - expect(onError).toHaveBeenCalledTimes(expectErrors) + try { + expect(onError).toHaveBeenCalledTimes(expectErrors) + } catch (err) { + // print errors + debug('onError calls:', onError.mock.calls) + throw err + } }) afterEach(async () => { @@ -762,6 +766,17 @@ describe('Connection', () => { expect(s.getState()).toBe('connected') }) + it('auto-disconnects when all handles removed without explicit connect first', async () => { + s.options.autoDisconnect = true + s.options.autoConnect = true + expect(s.getState()).toBe('disconnected') + await s.addHandle(1) + await s.send('test') + expect(s.getState()).toBe('connected') + await s.removeHandle(1) + expect(s.getState()).toBe('disconnected') + }) + it('handles concurrent call to removeHandle then connect', async () => { s.options.autoDisconnect = true s.options.autoConnect = true @@ -786,7 +801,7 @@ describe('Connection', () => { s.connect(), s.removeHandle(1), ]) - expect(s.getState()).toBe('disconnected') + expect(s.getState()).toBe('connected') expect(s.options.autoConnect).toBeTruthy() }) @@ -839,6 +854,38 @@ describe('Connection', () => { expect(s.options.autoConnect).not.toBeTruthy() }) + it('handles concurrent call to removeHandle', async () => { + s.options.autoDisconnect = true + s.options.autoConnect = true + await s.connect() + expect(s.getState()).toBe('connected') + await s.addHandle(1) + await Promise.all([ + s.removeHandle(1), + s.addHandle(1), + s.connect(), + s.removeHandle(1), + ]) + expect(s.getState()).toBe('connected') + expect(s.options.autoConnect).toBeTruthy() + }) + + it('late disconnect', async () => { + s.options.autoDisconnect = false + s.options.autoConnect = false + await s.connect() + expect(s.getState()).toBe('connected') + await s.addHandle(1) + expect(s.getState()).toBe('connected') + await s.addHandle(2) + expect(s.getState()).toBe('connected') + await s.removeHandle(2) + expect(s.getState()).toBe('connected') + await s.removeHandle(1) + expect(s.getState()).toBe('connected') + expect(s.options.autoConnect).not.toBeTruthy() + }) + it('does nothing if autoDisconnect is false', async () => { s.options.autoDisconnect = false s.options.autoConnect = false From a4a5646f6fd39791163eb57bc3d5f30cd16a4a09 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 27 Oct 2020 12:29:31 -0600 Subject: [PATCH 114/517] Make wanted connection state explicitly set by connect/disconnect, only called by user. Disable both auto connect/disconnect on explicit connect/disconnect. --- src/Connection.js | 110 ++++++++++++++++++++--------------- test/unit/Connection.test.js | 100 +++++++++++-------------------- 2 files changed, 98 insertions(+), 112 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index 813e04529..1dbf11756 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -100,6 +100,12 @@ const DEFAULT_MAX_RETRIES = 10 * waits for pending close/open before continuing */ +const STATE = { + ANY: 'ANY', + CONNECTED: 'CONNECTED', + DISCONNECTED: 'DISCONNECTED', +} + export default class Connection extends EventEmitter { static getOpen() { return openSockets @@ -109,7 +115,7 @@ export default class Connection extends EventEmitter { super() this.options = options this.options.autoConnect = !!this.options.autoConnect - this.shouldConnect = false + this.wantsState = STATE.ANY this.retryCount = 1 this._isReconnecting = false const id = uniqueId('Connection') @@ -153,23 +159,34 @@ export default class Connection extends EventEmitter { } emitTransition(event, ...args) { - const previousConnectionState = this.shouldConnect + const previousConnectionState = this.wantsState + if (previousConnectionState === STATE.ANY) { + return this.emit(event, ...args) + } + const result = this.emit(event, ...args) - // if event emitter changed shouldConnect state, throw - if (!!previousConnectionState !== !!this.shouldConnect) { - this.debug('transitioned in event handler %s: shouldConnect %s -> %s', event, previousConnectionState, this.shouldConnect) - if (this.shouldConnect) { + if (this.wantsState === STATE.ANY) { + return result + } + + // if event emitter changed wantsState state, throw + if (previousConnectionState !== this.wantsState) { + this.debug('transitioned in event handler %s: wantsState %s -> %s', event, previousConnectionState, this.wantsState) + if (this.wantsState === STATE.CONNECTED) { throw new ConnectionError(`connect called in ${event} handler`) } - throw new ConnectionError(`disconnect called in ${event} handler`) + + if (this.wantsState === STATE.DISCONNECTED) { + throw new ConnectionError(`disconnect called in ${event} handler`) + } } + return result } checkDone() { if (!this.isDone && !this._isReconnecting && !this.disconnectTask) { this.isDone = true - this.shouldConnect = false this.emit('done') } } @@ -179,11 +196,13 @@ export default class Connection extends EventEmitter { */ async connect() { - this.shouldConnect = true - if (this.connectedOnce) { - this.options.autoDisconnect = false // reset auto-connect on manual disconnect - } - this.connectedOnce = true + this.wantsState = STATE.CONNECTED + this.options.autoConnect = false + this.options.autoDisconnect = false + return this._connect() + } + + async _connect() { this.shouldReconnect = true this.isDone = false @@ -199,14 +218,11 @@ export default class Connection extends EventEmitter { this.debug('error while opening', err) // reconnect on initial connection failure - if (!this.shouldConnect) { + if (this.wantsState === STATE.DISCONNECTED) { throw err } this.debug = this._debug - if (this.initialConnectTask === initialConnectTask) { - this.initialConnectTask = undefined - } // eslint-disable-next-line promise/no-nesting return this.reconnect().catch((error) => { @@ -230,7 +246,7 @@ export default class Connection extends EventEmitter { } const connectTask = (async () => { - if (!this.shouldConnect) { + if (this.wantsState === STATE.DISCONNECTED) { throw new ConnectionError('disconnected before connected') } @@ -245,11 +261,11 @@ export default class Connection extends EventEmitter { debug('closed') } - return this._connect().then((value) => { + return this.__connect().then((value) => { const { socket } = this // capture so we can ignore if not current socket.addEventListener('close', async () => { // reconnect on unexpected failure - if ((this.socket && socket !== this.socket) || !this.shouldConnect) { + if ((this.socket && socket !== this.socket) || this.wantsState !== STATE.CONNECTED) { return } @@ -273,14 +289,14 @@ export default class Connection extends EventEmitter { return this.connectTask } - async _connect() { + async __connect() { this.debug = this._debug.extend(uniqueId('socket')) const { debug } = this await true // wait a tick debug('connecting...', this.options.url) this.emitTransition('connecting') - if (!this.shouldConnect) { + if (this.wantsState === STATE.DISCONNECTED) { // was disconnected in connecting event throw new ConnectionError('disconnected before connected') } @@ -288,8 +304,10 @@ export default class Connection extends EventEmitter { const socket = await OpenWebSocket(this.options.url, { perMessageDeflate: false, }) - debug('connected') - if (!this.shouldConnect) { + + debug('connected', this.wantsState) + + if (this.wantsState === STATE.DISCONNECTED) { await CloseWebSocket(socket) // was disconnected while connecting throw new ConnectionError('disconnected before connected') @@ -352,17 +370,10 @@ export default class Connection extends EventEmitter { }) } - async triggerConnectionOrWait() { - return Promise.all([ - this.nextConnection(), - this.maybeConnect() - ]) - } - async maybeConnect() { - if (this.options.autoConnect || this.shouldConnect) { + if (this.wantsState === STATE.CONNECTED || (this.options.autoConnect && this.wantsState !== STATE.DISCONNECTED)) { // should be open, so wait for open or trigger new open - await this.connect() + await this._connect() } } @@ -371,8 +382,8 @@ export default class Connection extends EventEmitter { if (!this.isConnected()) { const { autoConnect } = this.options let autoConnectMsg = `autoConnect is ${autoConnect}.` - if (this.didDisableAutoConnectAfterDisconnect) { - autoConnectMsg += ' Disabled automatically after explicit call to disconnect().' + if (this.didDisableAutoConnect) { + autoConnectMsg += ' Disabled automatically after explicit call to connect/disconnect().' } // note we can't just let socket.send fail, // have to do this check ourselves because the error appears @@ -404,10 +415,10 @@ export default class Connection extends EventEmitter { */ async disconnect() { - this.didDisableAutoConnectAfterDisconnect = !!this.options.autoConnect + this.didDisableAutoConnect = !!this.options.autoConnect this.options.autoConnect = false // reset auto-connect on manual disconnect this.options.autoDisconnect = false // reset auto-disconnect on manual disconnect - this.shouldConnect = false + this.wantsState = STATE.DISCONNECTED this.shouldReconnect = true this._isReconnecting = false @@ -416,7 +427,7 @@ export default class Connection extends EventEmitter { } let hadError = false - const disconnectTask = this.__disconnect() + const disconnectTask = this._disconnect() .catch(async (err) => { hadError = true return this.onDisconnectError(err) @@ -432,7 +443,7 @@ export default class Connection extends EventEmitter { return this.disconnectTask } - async __disconnect() { + async _disconnect() { if (this.connectTask) { try { await this.connectTask @@ -441,7 +452,7 @@ export default class Connection extends EventEmitter { } } - if (this.shouldConnect) { + if (this.wantsState === STATE.CONNECTED) { throw new ConnectionError('connect before disconnect started') } @@ -449,13 +460,13 @@ export default class Connection extends EventEmitter { this.emitTransition('disconnecting') } - if (this.shouldConnect) { + if (this.wantsState === STATE.CONNECTED) { throw new ConnectionError('connect while disconnecting') } await CloseWebSocket(this.socket) - if (this.shouldConnect) { + if (this.wantsState === STATE.CONNECTED) { throw new ConnectionError('connect before disconnected') } } @@ -506,7 +517,7 @@ export default class Connection extends EventEmitter { } // closed, noop - if (!this.shouldConnect) { + if (this.wantsState === STATE.DISCONNECTED) { this._isReconnecting = false return Promise.resolve() } @@ -517,7 +528,7 @@ export default class Connection extends EventEmitter { await this.backoffWait() // re-check if closed or closing - if (!this.shouldConnect) { + if (this.wantsState === STATE.DISCONNECTED) { this._isReconnecting = false return Promise.resolve() } @@ -588,6 +599,7 @@ export default class Connection extends EventEmitter { async addHandle(id) { this.shouldReconnect = true this.connectionHandles.add(id) + await this.maybeConnect() } /** @@ -597,7 +609,7 @@ export default class Connection extends EventEmitter { async removeHandle(id) { const hadConnection = this.connectionHandles.has(id) this.connectionHandles.delete(id) - if (hadConnection && this.connectionHandles.size === 0 && this.options.autoDisconnect) { + if (hadConnection && this._couldAutoDisconnect()) { return this._autoDisconnect() } @@ -619,16 +631,20 @@ export default class Connection extends EventEmitter { return Promise.resolve() } + _couldAutoDisconnect() { + return this.options.autoDisconnect && this.wantsState !== STATE.CONNECTED && this.connectionHandles.size === 0 + } + async _autoDisconnect() { if (this.autoDisconnectTask) { return this.autoDisconnectTask } this.autoDisconnectTask = Promise.resolve().then(async () => { - this.shouldReconnect = false await this.waitForPending() // eslint-disable-next-line promise/always-return - if (this.connectionHandles.size === 0 && !this.shouldReconnect && this.options.autoDisconnect) { + if (this._couldAutoDisconnect()) { + this.shouldReconnect = false await CloseWebSocket(this.socket) } }).catch(async (err) => { diff --git a/test/unit/Connection.test.js b/test/unit/Connection.test.js index 8cecac72d..a88557942 100644 --- a/test/unit/Connection.test.js +++ b/test/unit/Connection.test.js @@ -397,49 +397,6 @@ describe('Connection', () => { }) }) - describe('triggerConnectionOrWait', () => { - it('connects if no autoconnect', async () => { - s.options.autoConnect = false - const task = s.triggerConnectionOrWait() - expect(s.getState()).toBe('disconnected') - await wait(20) - await Promise.all([ - task, - s.connect() - ]) - expect(s.getState()).toBe('connected') - }) - - it('connects if autoconnect', async () => { - s.options.autoConnect = true - await s.triggerConnectionOrWait() - expect(s.getState()).toBe('connected') - }) - - it('errors if connect errors', async () => { - expectErrors = 1 - s.options.autoConnect = true - s.options.url = 'badurl' - await expect(async () => { - await s.triggerConnectionOrWait() - }).rejects.toThrow() - expect(s.getState()).toBe('disconnected') - }) - - it('errors if connect errors without autoconnect', async () => { - expectErrors = 1 - s.options.autoConnect = false - s.options.url = 'badurl' - const task = s.triggerConnectionOrWait() - await wait(20) - await expect(async () => { - await s.connect() - }).rejects.toThrow() - await expect(task).rejects.toThrow() - expect(s.getState()).toBe('disconnected') - }) - }) - describe('nextConnection', () => { it('resolves on next connection', async () => { let resolved = false @@ -736,14 +693,14 @@ describe('Connection', () => { s.options.autoDisconnect = true s.options.autoConnect = true expect(s.getState()).toBe('disconnected') - await s.addHandle(1) await s.removeHandle(1) // noop - await s.connect() - // must have had handle previously to disconnect - await s.removeHandle(1) - expect(s.getState()).toBe('connected') + expect(s.getState()).toBe('disconnected') await s.addHandle(1) + // must have had handle previously to disconnect + await s.removeHandle(2) expect(s.getState()).toBe('connected') + await s.removeHandle(1) + expect(s.getState()).toBe('disconnected') // can take multiple of the same handle (no error) await s.addHandle(1) expect(s.getState()).toBe('connected') @@ -766,7 +723,7 @@ describe('Connection', () => { expect(s.getState()).toBe('connected') }) - it('auto-disconnects when all handles removed without explicit connect first', async () => { + it('auto-disconnects when all handles removed without explicit connect', async () => { s.options.autoDisconnect = true s.options.autoConnect = true expect(s.getState()).toBe('disconnected') @@ -775,12 +732,18 @@ describe('Connection', () => { expect(s.getState()).toBe('connected') await s.removeHandle(1) expect(s.getState()).toBe('disconnected') + await s.addHandle(1) + await s.send('test') + expect(s.getState()).toBe('connected') + await s.removeHandle(1) + expect(s.getState()).toBe('disconnected') + await s.send('test') + expect(s.getState()).toBe('connected') }) it('handles concurrent call to removeHandle then connect', async () => { s.options.autoDisconnect = true s.options.autoConnect = true - await s.connect() await s.addHandle(1) await Promise.all([ s.removeHandle(1), @@ -788,6 +751,12 @@ describe('Connection', () => { ]) expect(s.getState()).toBe('connected') + // auto-disconnect disabled after connect + await s.addHandle(1) + await s.removeHandle(1) + expect(s.getState()).toBe('connected') + expect(s.options.autoConnect).not.toBeTruthy() + expect(s.options.autoDisconnect).not.toBeTruthy() }) it('handles concurrent call to connect then removeHandle', async () => { @@ -802,7 +771,8 @@ describe('Connection', () => { s.removeHandle(1), ]) expect(s.getState()).toBe('connected') - expect(s.options.autoConnect).toBeTruthy() + expect(s.options.autoConnect).not.toBeTruthy() + expect(s.options.autoDisconnect).not.toBeTruthy() }) it('handles concurrent call to disconnect then removeHandle', async () => { @@ -818,6 +788,7 @@ describe('Connection', () => { ]) expect(s.getState()).toBe('disconnected') expect(s.options.autoConnect).not.toBeTruthy() + expect(s.options.autoDisconnect).not.toBeTruthy() }) it('handles concurrent call to removeHandle then disconnect', async () => { @@ -828,11 +799,12 @@ describe('Connection', () => { expect(s.getState()).toBe('connected') await s.addHandle(1) await Promise.all([ - s.disconnect(), s.removeHandle(1), + s.disconnect(), ]) expect(s.getState()).toBe('disconnected') expect(s.options.autoConnect).not.toBeTruthy() + expect(s.options.autoDisconnect).not.toBeTruthy() }) it('handles concurrent call to removeHandle then disconnect + connect', async () => { @@ -867,30 +839,27 @@ describe('Connection', () => { s.removeHandle(1), ]) expect(s.getState()).toBe('connected') - expect(s.options.autoConnect).toBeTruthy() + expect(s.options.autoConnect).not.toBeTruthy() }) it('late disconnect', async () => { - s.options.autoDisconnect = false - s.options.autoConnect = false - await s.connect() - expect(s.getState()).toBe('connected') + s.options.autoDisconnect = true + s.options.autoConnect = true await s.addHandle(1) - expect(s.getState()).toBe('connected') await s.addHandle(2) expect(s.getState()).toBe('connected') await s.removeHandle(2) expect(s.getState()).toBe('connected') - await s.removeHandle(1) - expect(s.getState()).toBe('connected') - expect(s.options.autoConnect).not.toBeTruthy() + const t = s.removeHandle(1) + await wait() + await s.disconnect() // disconnect while auto-disconnecting + await t + expect(s.getState()).toBe('disconnected') }) it('does nothing if autoDisconnect is false', async () => { s.options.autoDisconnect = false - s.options.autoConnect = false - await s.connect() - expect(s.getState()).toBe('connected') + s.options.autoConnect = true await s.addHandle(1) expect(s.getState()).toBe('connected') await s.addHandle(2) @@ -899,7 +868,8 @@ describe('Connection', () => { expect(s.getState()).toBe('connected') await s.removeHandle(1) expect(s.getState()).toBe('connected') - expect(s.options.autoConnect).not.toBeTruthy() + expect(s.options.autoConnect).toBeTruthy() + expect(s.options.autoDisconnect).not.toBeTruthy() }) }) From c77a74d2b7cd917ce070c529a67e8645cb2f81c3 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 27 Oct 2020 12:49:15 -0600 Subject: [PATCH 115/517] Add isConnectionValid method for knowing when should unsub vs disconnect. --- src/Connection.js | 19 +++++++++++++++++-- test/unit/Connection.test.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index 1dbf11756..131dd8db1 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -370,8 +370,19 @@ export default class Connection extends EventEmitter { }) } + isConnectionValid() { + return !!( + this.wantsState !== STATE.DISCONNECTED + && ( + this.wantsState === STATE.CONNECTED + || this.options.autoConnect + || this.isConnected() + ) + ) + } + async maybeConnect() { - if (this.wantsState === STATE.CONNECTED || (this.options.autoConnect && this.wantsState !== STATE.DISCONNECTED)) { + if (this.isConnectionValid()) { // should be open, so wait for open or trigger new open await this._connect() } @@ -632,7 +643,11 @@ export default class Connection extends EventEmitter { } _couldAutoDisconnect() { - return this.options.autoDisconnect && this.wantsState !== STATE.CONNECTED && this.connectionHandles.size === 0 + return !!( + this.options.autoDisconnect + && this.wantsState !== STATE.CONNECTED + && this.connectionHandles.size === 0 + ) } async _autoDisconnect() { diff --git a/test/unit/Connection.test.js b/test/unit/Connection.test.js index a88557942..74f055e7a 100644 --- a/test/unit/Connection.test.js +++ b/test/unit/Connection.test.js @@ -397,6 +397,39 @@ describe('Connection', () => { }) }) + describe('isConnectionValid', () => { + it('works with explicit connect/disconnect', async () => { + expect(s.isConnectionValid()).not.toBeTruthy() + const onConnect = s.connect() + expect(s.isConnectionValid()).toBeTruthy() + await onConnect + expect(s.isConnectionValid()).toBeTruthy() + const onDisconnect = s.disconnect() + expect(s.isConnectionValid()).not.toBeTruthy() + await onDisconnect + expect(s.isConnectionValid()).not.toBeTruthy() + }) + + it('works with autoConnect', async () => { + s.options.autoConnect = true + expect(s.isConnectionValid()).toBeTruthy() + }) + + it('works with autoDisconnect', async () => { + s.options.autoConnect = true + s.options.autoDisconnect = true + expect(s.isConnectionValid()).toBeTruthy() + await s.addHandle(1) + expect(s.isConnectionValid()).toBeTruthy() + await s.removeHandle(1) + expect(s.isConnectionValid()).toBeTruthy() + const onDisconnect = s.disconnect() + expect(s.isConnectionValid()).not.toBeTruthy() + await onDisconnect + expect(s.isConnectionValid()).not.toBeTruthy() + }) + }) + describe('nextConnection', () => { it('resolves on next connection', async () => { let resolved = false From 866d1e13ec839bcc3650431a9946874b8b150032 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 27 Oct 2020 13:21:21 -0600 Subject: [PATCH 116/517] Partial Subscription auto disconnect test/implementation. --- src/subscribe/index.js | 36 +++++++++++++++----------- test/integration/Stream.test.js | 46 +++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 15 deletions(-) diff --git a/src/subscribe/index.js b/src/subscribe/index.js index 8c841c171..b63056749 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -88,6 +88,11 @@ function messageStream(connection, { streamId, streamPartition, type = ControlMe .once('finish', onClose) } +function SubKey({ streamId, streamPartition = 0 }) { + if (streamId == null) { throw new Error(`SubKey: invalid streamId: ${streamId} ${streamPartition}`) } + return `${streamId}::${streamPartition}` +} + export function validateOptions(optionsOrStreamId) { if (!optionsOrStreamId) { throw new Error('options is required!') @@ -127,6 +132,8 @@ export function validateOptions(optionsOrStreamId) { throw new Error(`streamId must be set, given: ${optionsOrStreamId}`) } + options.key = SubKey(options) + return options } @@ -467,6 +474,7 @@ class Subscription extends Emitter { this.client = client this.onFinal = onFinal this.options = validateOptions(options) + this.key = this.options.key this.streams = new Set() this.queue = pLimit(1) @@ -498,8 +506,9 @@ class Subscription extends Emitter { } async onDisconnecting() { - if (!this.client.connection.shouldConnect) { - this.cancel() + // should actually reconnect + if (!this.client.connection.isConnectionValid()) { + await this.cancel() } } @@ -519,20 +528,21 @@ class Subscription extends Emitter { return unsubscribe(this.client, this.options) } - _cleanupConnectionHandlers() { + _cleanupHandlers() { // eslint-disable-line class-methods-use-this // noop will be replaced in subscribe } async _subscribe() { pMemoize.clear(this.sendUnsubscribe) const { connection } = this.client - this._cleanupConnectionHandlers = connection.onTransition({ + this._cleanupHandlers = connection.onTransition({ connection: this.client.connection, onConnected: this.onConnected, onDisconnected: this.onDisconnected, onDisconnecting: this.onDisconnecting, onDone: this.onConnectionDone, }) + await connection.addHandle(this.key) await this.sendSubscribe() } @@ -550,8 +560,9 @@ class Subscription extends Emitter { async onSubscriptionDone() { pMemoize.clear(this.sendSubscribe) - this._cleanupConnectionHandlers() - if (this.client.connection.shouldConnect) { + this._cleanupHandlers() + await this.client.connection.removeHandle(this.key) + if (this.client.connection.isConnectionValid()) { await this.sendUnsubscribe() } } @@ -561,7 +572,7 @@ class Subscription extends Emitter { } async cancel(optionalErr) { - this._cleanupConnectionHandlers() + this._cleanupHandlers() if (this.hasPending()) { await this.queue(() => {}) } @@ -608,11 +619,6 @@ class Subscription extends Emitter { } } -function SubKey({ streamId, streamPartition = 0 }) { - if (streamId == null) { throw new Error(`SubKey: invalid streamId: ${streamId} ${streamPartition}`) } - return `${streamId}::${streamPartition}` -} - /** * Top-level interface for creating/destroying subscriptions. */ @@ -628,7 +634,7 @@ export default class Subscriptions { } get(options) { - const key = SubKey(validateOptions(options)) + const { key } = validateOptions(options) return this.subscriptions.get(key) } @@ -638,7 +644,7 @@ export default class Subscriptions { } async unsubscribe(options) { - const key = SubKey(validateOptions(options)) + const { key } = validateOptions(options) const sub = this.subscriptions.get(key) if (!sub) { return @@ -648,7 +654,7 @@ export default class Subscriptions { } async subscribe(options) { - const key = SubKey(validateOptions(options)) + const { key } = validateOptions(options) let sub = this.subscriptions.get(key) if (!sub) { sub = new Subscription(this.client, options, () => { diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index 648e19c76..0309955ad 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -163,6 +163,52 @@ describeRepeats('StreamrClient Stream', () => { expect(received).toEqual(published) }) + it('errors if not connected', async () => { + await client.disconnect() + await expect(() => ( + M.subscribe(stream) + )).rejects.toThrow('connect') + expect(M.count(stream.id)).toBe(0) + }) + }) + + describe('auto connect/disconnect', () => { + beforeEach(async () => { + await client.disconnect() + // eslint-disable-next-line require-atomic-updates + client = createClient({ + autoConnect: true, + autoDisconnect: true, + }) + + M = new MessageStream(client) + await client.session.getSessionToken() + stream = await client.createStream({ + name: uid('stream') + }) + publishTestMessages = getPublishTestMessages(client, stream.id) + }) + + it('connects on subscribe, disconnects on end', async () => { + const sub = await M.subscribe(stream.id) + expect(client.connection.getState()).toBe('connected') + expect(M.count(stream.id)).toBe(1) + + const published = await publishTestMessages() + + const received = [] + for await (const m of sub) { + received.push(m) + if (received.length === published.length) { + return + } + } + expect(received).toEqual(published) + expect(client.connection.getState()).toBe('disconnected') + }) + }) + + describe('ending a subscription', () => { it('can kill stream using async end', async () => { const sub = await M.subscribe(stream.id) expect(M.count(stream.id)).toBe(1) From 4184a2821a90391bf8e12b5db5c8c1a8cf09742f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 27 Oct 2020 13:54:00 -0600 Subject: [PATCH 117/517] Implement auto-connect/disconnect for subscriptions. --- src/Connection.js | 4 +- src/StreamrClient.js | 19 ++++- src/subscribe/index.js | 112 +++++++++++++++++++----------- test/integration/Stream.test.js | 34 +++++++++ test/unit/StreamrClient.test.js | 22 +++--- test/unit/StubbedStreamrClient.js | 13 ++-- 6 files changed, 147 insertions(+), 57 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index 131dd8db1..730df4f70 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -292,7 +292,7 @@ export default class Connection extends EventEmitter { async __connect() { this.debug = this._debug.extend(uniqueId('socket')) const { debug } = this - await true // wait a tick + await true // wait a microtask debug('connecting...', this.options.url) this.emitTransition('connecting') @@ -305,7 +305,7 @@ export default class Connection extends EventEmitter { perMessageDeflate: false, }) - debug('connected', this.wantsState) + debug('socket connected') if (this.wantsState === STATE.DISCONNECTED) { await CloseWebSocket(socket) diff --git a/src/StreamrClient.js b/src/StreamrClient.js index c14717c2c..c31c9a753 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -223,8 +223,23 @@ export default class StreamrClient extends EventEmitter { return this.publisher.getPublisherId() } - subscribe(...args) { - return this.messageStream.subscribe(...args) + async subscribe(opts, fn) { + this.emit('subscribing') + const subTask = this.messageStream.subscribe(opts) + if (!fn) { + const sub = await subTask + this.emit('subscribed') + return sub + } + Promise.resolve(subTask).then(async (sub) => { + for await (const msg of sub) { + await fn(msg.getParsedContent(), msg) + } + return sub + }).catch((err) => { + this.emit('error', err) + }) + return subTask } unsubscribe(...args) { diff --git a/src/subscribe/index.js b/src/subscribe/index.js index b63056749..d7d25b2da 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -6,7 +6,7 @@ import pLimit from 'p-limit' import { ControlLayer, MessageLayer, Utils, Errors } from 'streamr-client-protocol' import SignatureRequiredError from '../errors/SignatureRequiredError' -import { uuid, CacheAsyncFn, pOrderedResolve } from '../utils' +import { uuid, CacheAsyncFn, pOrderedResolve, Defer } from '../utils' import { endStream, pipeline, CancelableGenerator } from '../utils/iterators' const { OrderingUtil, StreamMessageValidator } = Utils @@ -152,8 +152,10 @@ const PAIRS = new Map([ */ async function waitForResponse({ connection, types, requestId }) { + await connection.nextConnection() return new Promise((resolve, reject) => { let cleanup + let onDisconnected const onResponse = (res) => { if (res.requestId !== requestId) { return } // clean up err handler @@ -171,6 +173,7 @@ async function waitForResponse({ connection, types, requestId }) { } cleanup = () => { + connection.off('disconnected', onDisconnected) types.forEach((type) => { connection.off(type, onResponse) }) @@ -181,6 +184,13 @@ async function waitForResponse({ connection, types, requestId }) { connection.on(type, onResponse) }) connection.on(ControlMessage.TYPES.ErrorResponse, onErrorResponse) + + onDisconnected = () => { + cleanup() + reject(new Error('disconnected before got response')) + } + + connection.once('disconnected', onDisconnected) }) } @@ -486,13 +496,14 @@ class Subscription extends Emitter { this.sendSubscribe = pMemoize(this.sendSubscribe.bind(this)) this.sendUnsubscribe = pMemoize(this.sendUnsubscribe.bind(this)) this.validate = Validator(client, options) - this.onConnected = this.onConnected.bind(this) - this.onDisconnected = this.onDisconnected.bind(this) - this.onDisconnecting = this.onDisconnecting.bind(this) - this.onConnectionDone = this.onConnectionDone.bind(this) + this._onConnected = this._onConnected.bind(this) + this._onDisconnected = this._onDisconnected.bind(this) + this._onDisconnecting = this._onDisconnecting.bind(this) + this._onConnectionDone = this._onConnectionDone.bind(this) + this._didSubscribe = false } - async onConnected() { + async _onConnected() { try { await this.sendSubscribe() } catch (err) { @@ -500,20 +511,20 @@ class Subscription extends Emitter { } } - async onDisconnected() { + async _onDisconnected() { // unblock subscribe pMemoize.clear(this.sendSubscribe) } - async onDisconnecting() { - // should actually reconnect + async _onDisconnecting() { + // otherwise should eventually reconnect if (!this.client.connection.isConnectionValid()) { await this.cancel() } } - onConnectionDone() { - this.cancel() + async _onConnectionDone() { + await this.cancel() } hasPending() { @@ -521,11 +532,15 @@ class Subscription extends Emitter { } async sendSubscribe() { - return subscribe(this.client, this.options) + await subscribe(this.client, this.options) } async sendUnsubscribe() { - return unsubscribe(this.client, this.options) + const { connection } = this.client + // disconnection auto-unsubs, so if already disconnected/disconnecting no need to send unsub + if (connection.isConnectionValid() && !connection.isDisconnected() && !connection.isDisconnecting()) { + await unsubscribe(this.client, this.options) + } } _cleanupHandlers() { // eslint-disable-line class-methods-use-this @@ -533,17 +548,23 @@ class Subscription extends Emitter { } async _subscribe() { - pMemoize.clear(this.sendUnsubscribe) const { connection } = this.client - this._cleanupHandlers = connection.onTransition({ - connection: this.client.connection, - onConnected: this.onConnected, - onDisconnected: this.onDisconnected, - onDisconnecting: this.onDisconnecting, - onDone: this.onConnectionDone, - }) - await connection.addHandle(this.key) - await this.sendSubscribe() + try { + pMemoize.clear(this.sendUnsubscribe) + this._cleanupHandlers = connection.onTransition({ + connection: this.client.connection, + onConnected: this._onConnected, + onDisconnected: this._onDisconnected, + onDisconnecting: this._onDisconnecting, + onDone: this._onConnectionDone, + }) + await connection.addHandle(this.key) + await this.sendSubscribe() + this._didSubscribe = true + } catch (err) { + await this._cleanupFinal() + throw err + } } async subscribe() { @@ -558,15 +579,6 @@ class Subscription extends Emitter { }), 'return failed') } - async onSubscriptionDone() { - pMemoize.clear(this.sendSubscribe) - this._cleanupHandlers() - await this.client.connection.removeHandle(this.key) - if (this.client.connection.isConnectionValid()) { - await this.sendUnsubscribe() - } - } - async unsubscribe(...args) { return this.cancel(...args) } @@ -581,13 +593,25 @@ class Subscription extends Emitter { )), 'cancel failed') } + async _onSubscriptionDone() { + const didSubscribe = !!this._didSubscribe + pMemoize.clear(this.sendSubscribe) + this._cleanupHandlers() + await this.client.connection.removeHandle(this.key) + if (!didSubscribe) { return } + + if (this.client.connection.isConnectionValid()) { + await this.sendUnsubscribe() + } + } + async _cleanupFinal() { - this.onFinal() // unsubscribe if no more streams - await this.onSubscriptionDone() + await this._onSubscriptionDone() + return this.onFinal() } - async _cleanup(it) { + async _cleanupIterator(it) { // if iterator never started, finally block never called, thus need to manually clean it const hadStream = this.streams.has(it) this.streams.delete(it) @@ -605,13 +629,17 @@ class Subscription extends Emitter { validate: this.validate, type: ControlMessage.TYPES.BroadcastMessage, ...this.options, - }, async () => { - await this._cleanup(msgs) - }) + }, async () => ( + this._cleanupIterator(msgs) + )) this.streams.add(msgs) - return msgs + return Object.assign(msgs, { + count: this.count.bind(this), + unsubscribe: this.unsubscribe.bind(this), + subscribe: this.subscribe.bind(this), + }) } [Symbol.asyncIterator]() { @@ -629,7 +657,11 @@ export default class Subscriptions { this.subscriptions = new Map() } - getAll() { + getAll(options) { + if (options) { + return [this.get(options)].filter(Boolean) + } + return [...this.subscriptions.values()] } diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index 0309955ad..73d5a226e 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -206,6 +206,40 @@ describeRepeats('StreamrClient Stream', () => { expect(received).toEqual(published) expect(client.connection.getState()).toBe('disconnected') }) + + it('connects on subscribe, disconnects on end with two subs', async () => { + const [sub1, sub2] = await Promise.all([ + M.subscribe(stream.id), + M.subscribe(stream.id), + ]) + + expect(client.connection.getState()).toBe('connected') + expect(M.count(stream.id)).toBe(2) + + const published = await publishTestMessages() + + const received1 = [] + for await (const m of sub1) { + received1.push(m) + if (received1.length === published.length) { + return + } + } + + expect(received1).toEqual(published) + expect(client.connection.getState()).toBe('connected') + + const received2 = [] + for await (const m of sub2) { + received2.push(m) + if (received2.length === published.length) { + return + } + } + expect(client.connection.getState()).toBe('disconnected') + + expect(received2).toEqual(received1) + }) }) describe('ending a subscription', () => { diff --git a/test/unit/StreamrClient.test.js b/test/unit/StreamrClient.test.js index 492d6bbec..1ff9bd221 100644 --- a/test/unit/StreamrClient.test.js +++ b/test/unit/StreamrClient.test.js @@ -2,6 +2,7 @@ import sinon from 'sinon' import { Wallet } from 'ethers' import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' import { wait, waitForEvent } from 'streamr-test-utils' +import Debug from 'debug' import FailedToPublishError from '../../src/errors/FailedToPublishError' import Subscription from '../../src/Subscription' @@ -32,6 +33,8 @@ const { const { StreamMessage, MessageRef, MessageID, MessageIDStrict } = MessageLayer +console.log = Debug('Streamr:: CONSOLE ') + describe('StreamrClient', () => { let client let connection @@ -63,7 +66,10 @@ describe('StreamrClient', () => { }) c.emitMessage = (message) => { - c.emit(message.type, message) + c.emit('message', { + type: 'message', + data: message.serialize(), + }) } return c @@ -138,11 +144,11 @@ describe('StreamrClient', () => { }) describe('connecting behaviour', () => { - it('connected event should emit an event on client', async (done) => { - client.once('connected', () => { - done() - }) + it('connected event should emit an event on client', async () => { + const onConnected = jest.fn() + client.once('connected', onConnected) await client.connect() + expect(onConnected).toHaveBeenCalledTimes(1) }) it('should not send anything if not subscribed to anything', async () => { @@ -227,7 +233,7 @@ describe('StreamrClient', () => { }) }) - describe('disconnection behaviour', () => { + describe.only('disconnection behaviour', () => { beforeEach(async () => client.connect()) it('emits disconnected event on client', async (done) => { @@ -245,10 +251,10 @@ describe('StreamrClient', () => { const sub = await mockSubscription('stream1', () => {}) client.connection.socket.close() await waitForEvent(client, 'disconnected') - expect(client.getSubscriptions(sub.streamId)).toEqual([sub]) + expect(client.getSubscriptions(sub.streamId)).toHaveLength(1) expect(sub.getState()).toEqual(Subscription.State.unsubscribed) await client.connect() - expect(client.getSubscriptions(sub.streamId)).toEqual([sub]) + expect(client.getSubscriptions(sub.streamId)).toHaveLength(1) // re-subscribes expect(sub.getState()).toEqual(Subscription.State.subscribing) }) diff --git a/test/unit/StubbedStreamrClient.js b/test/unit/StubbedStreamrClient.js index 3f2525c7e..41c6691f9 100644 --- a/test/unit/StubbedStreamrClient.js +++ b/test/unit/StubbedStreamrClient.js @@ -1,5 +1,6 @@ import sinon from 'sinon' -import StreamrClient from '../../src/StreamrClient' + +import StreamrClient from '../../src/' import Stream from '../../src/rest/domain/Stream' export default class StubbedStreamrClient extends StreamrClient { @@ -9,10 +10,12 @@ export default class StubbedStreamrClient extends StreamrClient { }) } - getStream = sinon.stub().resolves(new Stream(null, { - id: 'streamId', - partitions: 1, - })) + async getStream () { + return new Stream(null, { + id: 'streamId', + partitions: 1, + }) + } } // publisherId is the hash of 'username' StubbedStreamrClient.hashedUsername = '0x16F78A7D6317F102BBD95FC9A4F3FF2E3249287690B8BDAD6B7810F82B34ACE3'.toLowerCase() From 27d95f0ef43c8d25bf2cb1a0a632106383f50e5c Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 27 Oct 2020 13:54:36 -0600 Subject: [PATCH 118/517] Update utils unit test. --- test/unit/utils.test.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test/unit/utils.test.js b/test/unit/utils.test.js index ac1541893..e9de15320 100644 --- a/test/unit/utils.test.js +++ b/test/unit/utils.test.js @@ -49,16 +49,18 @@ describe('utils', () => { expect(res.test).toBeTruthy() }) - it('should return 401 error when invalid session token is passed twice', (done) => { + it('should return 401 error when invalid session token is passed twice', async () => { session.getSessionToken = sinon.stub().resolves('invalid token') - return authFetch(baseUrl + testUrl, session).catch((err) => { + const onCaught = jest.fn() + await authFetch(baseUrl + testUrl, session).catch((err) => { + onCaught() expect(session.getSessionToken.calledTwice).toBeTruthy() expect(err.toString()).toMatch( `${baseUrl + testUrl} returned with error code 401. Unauthorized` ) expect(err.body).toEqual('Unauthorized') - done() }) + expect(onCaught).toHaveBeenCalledTimes(1) }) it('should return normally when valid session token is passed after expired session token', async () => { From 24dc66d42f485c61c87edc08d9e616dd62f837dc Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 27 Oct 2020 15:14:50 -0600 Subject: [PATCH 119/517] Make rest debug logs output on one line. --- src/rest/LoginEndpoints.js | 11 +++++++---- src/rest/StreamEndpoints.js | 24 ++++++++++++------------ 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/rest/LoginEndpoints.js b/src/rest/LoginEndpoints.js index 4d869e750..88e36c40d 100644 --- a/src/rest/LoginEndpoints.js +++ b/src/rest/LoginEndpoints.js @@ -18,7 +18,7 @@ async function getSessionToken(url, props) { } export async function getChallenge(address) { - this.debug('getChallenge', { + this.debug('getChallenge %o', { address, }) const url = getEndpointUrl(this.options.restUrl, 'login', 'challenge', address) @@ -32,7 +32,7 @@ export async function getChallenge(address) { } export async function sendChallengeResponse(challenge, signature, address) { - this.debug('sendChallengeResponse', { + this.debug('sendChallengeResponse %o', { challenge, signature, address, @@ -47,7 +47,7 @@ export async function sendChallengeResponse(challenge, signature, address) { } export async function loginWithChallengeResponse(signingFunction, address) { - this.debug('loginWithChallengeResponse', { + this.debug('loginWithChallengeResponse &o', { address, }) const challenge = await this.getChallenge(address) @@ -56,7 +56,7 @@ export async function loginWithChallengeResponse(signingFunction, address) { } export async function loginWithApiKey(apiKey) { - this.debug('loginWithApiKey', { + this.debug('loginWithApiKey %o', { apiKey, }) const url = getEndpointUrl(this.options.restUrl, 'login', 'apikey') @@ -67,6 +67,9 @@ export async function loginWithApiKey(apiKey) { } export async function loginWithUsernamePassword(username, password) { + this.debug('loginWithUsernamePassword %o', { + username, + }) const url = getEndpointUrl(this.options.restUrl, 'login', 'password') const props = { username, diff --git a/src/rest/StreamEndpoints.js b/src/rest/StreamEndpoints.js index 4a861db5c..5086b4a7e 100644 --- a/src/rest/StreamEndpoints.js +++ b/src/rest/StreamEndpoints.js @@ -37,7 +37,7 @@ function getKeepAliveAgentForUrl(url) { // These function are mixed in to StreamrClient.prototype. // In the below functions, 'this' is intended to be the StreamrClient export async function getStream(streamId) { - this.debug('getStream', { + this.debug('getStream %o', { streamId, }) const url = getEndpointUrl(this.options.restUrl, 'streams', streamId) @@ -53,7 +53,7 @@ export async function getStream(streamId) { } export async function listStreams(query = {}) { - this.debug('listStreams', { + this.debug('listStreams %o', { query, }) const url = getEndpointUrl(this.options.restUrl, 'streams') + '?' + qs.stringify(query) @@ -62,7 +62,7 @@ export async function listStreams(query = {}) { } export async function getStreamByName(name) { - this.debug('getStreamByName', { + this.debug('getStreamByName %o', { name, }) const json = await this.listStreams({ @@ -73,7 +73,7 @@ export async function getStreamByName(name) { } export async function createStream(props) { - this.debug('createStream', { + this.debug('createStream %o', { props, }) if (!props || !props.name) { @@ -92,7 +92,7 @@ export async function createStream(props) { } export async function getOrCreateStream(props) { - this.debug('getOrCreateStream', { + this.debug('getOrCreateStream %o', { props, }) let json @@ -119,7 +119,7 @@ export async function getOrCreateStream(props) { } export async function getStreamPublishers(streamId) { - this.debug('getStreamPublishers', { + this.debug('getStreamPublishers %o', { streamId, }) const url = getEndpointUrl(this.options.restUrl, 'streams', streamId, 'publishers') @@ -128,7 +128,7 @@ export async function getStreamPublishers(streamId) { } export async function isStreamPublisher(streamId, ethAddress) { - this.debug('isStreamPublisher', { + this.debug('isStreamPublisher %o', { streamId, ethAddress, }) @@ -146,7 +146,7 @@ export async function isStreamPublisher(streamId, ethAddress) { } export async function getStreamSubscribers(streamId) { - this.debug('getStreamSubscribers', { + this.debug('getStreamSubscribers %o', { streamId, }) const url = getEndpointUrl(this.options.restUrl, 'streams', streamId, 'subscribers') @@ -155,7 +155,7 @@ export async function getStreamSubscribers(streamId) { } export async function isStreamSubscriber(streamId, ethAddress) { - this.debug('isStreamSubscriber', { + this.debug('isStreamSubscriber %o', { streamId, ethAddress, }) @@ -172,7 +172,7 @@ export async function isStreamSubscriber(streamId, ethAddress) { } export async function getStreamValidationInfo(streamId) { - this.debug('getStreamValidationInfo', { + this.debug('getStreamValidationInfo %o', { streamId, }) const url = getEndpointUrl(this.options.restUrl, 'streams', streamId, 'validation') @@ -182,7 +182,7 @@ export async function getStreamValidationInfo(streamId) { export async function getStreamLast(streamObjectOrId) { const { streamId, streamPartition = 0, count = 1 } = validateOptions(streamObjectOrId) - this.debug('getStreamLast', { + this.debug('getStreamLast %o', { streamId, streamPartition, count, @@ -203,7 +203,7 @@ export async function publishHttp(streamObjectOrId, data, requestOptions = {}, k } else { streamId = streamObjectOrId } - this.debug('publishHttp', { + this.debug('publishHttp %o', { streamId, data, }) From c6a127ca7e96278295a5e321b733cef0341a9c08 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 27 Oct 2020 14:10:25 -0600 Subject: [PATCH 120/517] Fix subscribe tests, move autoconnect tests into StreamConnectionState. --- test/integration/Stream.test.js | 94 +++---------------- .../integration/StreamConnectionState.test.js | 67 +++++++++---- 2 files changed, 63 insertions(+), 98 deletions(-) diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index 73d5a226e..53f1f322f 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -103,9 +103,9 @@ describeRepeats('StreamrClient Stream', () => { const received = [] for await (const m of sub) { - received.push(m) + received.push(m.getParsedContent()) if (received.length === published.length) { - return + break } } expect(received).toEqual(published) @@ -121,9 +121,9 @@ describeRepeats('StreamrClient Stream', () => { const received = [] for await (const m of sub) { - received.push(m) + received.push(m.getParsedContent()) if (received.length === published.length) { - return + break } } expect(received).toEqual(published) @@ -137,9 +137,9 @@ describeRepeats('StreamrClient Stream', () => { const received = [] for await (const m of sub) { - received.push(m) + received.push(m.getParsedContent()) if (received.length === published.length) { - return + break } } expect(received).toEqual(published) @@ -154,9 +154,9 @@ describeRepeats('StreamrClient Stream', () => { const received = [] for await (const m of sub) { - received.push(m) + received.push(m.getParsedContent()) if (received.length === published.length) { - return + break } } @@ -172,76 +172,6 @@ describeRepeats('StreamrClient Stream', () => { }) }) - describe('auto connect/disconnect', () => { - beforeEach(async () => { - await client.disconnect() - // eslint-disable-next-line require-atomic-updates - client = createClient({ - autoConnect: true, - autoDisconnect: true, - }) - - M = new MessageStream(client) - await client.session.getSessionToken() - stream = await client.createStream({ - name: uid('stream') - }) - publishTestMessages = getPublishTestMessages(client, stream.id) - }) - - it('connects on subscribe, disconnects on end', async () => { - const sub = await M.subscribe(stream.id) - expect(client.connection.getState()).toBe('connected') - expect(M.count(stream.id)).toBe(1) - - const published = await publishTestMessages() - - const received = [] - for await (const m of sub) { - received.push(m) - if (received.length === published.length) { - return - } - } - expect(received).toEqual(published) - expect(client.connection.getState()).toBe('disconnected') - }) - - it('connects on subscribe, disconnects on end with two subs', async () => { - const [sub1, sub2] = await Promise.all([ - M.subscribe(stream.id), - M.subscribe(stream.id), - ]) - - expect(client.connection.getState()).toBe('connected') - expect(M.count(stream.id)).toBe(2) - - const published = await publishTestMessages() - - const received1 = [] - for await (const m of sub1) { - received1.push(m) - if (received1.length === published.length) { - return - } - } - - expect(received1).toEqual(published) - expect(client.connection.getState()).toBe('connected') - - const received2 = [] - for await (const m of sub2) { - received2.push(m) - if (received2.length === published.length) { - return - } - } - expect(client.connection.getState()).toBe('disconnected') - - expect(received2).toEqual(received1) - }) - }) - describe('ending a subscription', () => { it('can kill stream using async end', async () => { const sub = await M.subscribe(stream.id) @@ -253,7 +183,7 @@ describeRepeats('StreamrClient Stream', () => { const received = [] try { for await (const m of sub) { - received.push(m) + received.push(m.getParsedContent()) // after first message schedule end if (received.length === 1) { // eslint-disable-next-line no-loop-func @@ -285,7 +215,7 @@ describeRepeats('StreamrClient Stream', () => { const received = [] await expect(async () => { for await (const m of sub) { - received.push(m) + received.push(m.getParsedContent()) // after first message schedule end if (received.length === 1) { throw err @@ -322,7 +252,7 @@ describeRepeats('StreamrClient Stream', () => { expect(received1).toEqual(published) expect(received2).toEqual(received1) - // only subscribed once + // subscribed once expect(send.mock.calls.filter(([msg]) => ( msg.type === ControlMessage.TYPES.SubscribeRequest ))).toHaveLength(1) @@ -354,7 +284,7 @@ describeRepeats('StreamrClient Stream', () => { expect(received1).toEqual(published) expect(received2).toEqual(received1) - // only subscribed once + // subscribed once expect(send.mock.calls.filter(([msg]) => ( msg.type === ControlMessage.TYPES.SubscribeRequest ))).toHaveLength(1) diff --git a/test/integration/StreamConnectionState.test.js b/test/integration/StreamConnectionState.test.js index 5e6cb4812..2cf8b9509 100644 --- a/test/integration/StreamConnectionState.test.js +++ b/test/integration/StreamConnectionState.test.js @@ -35,9 +35,8 @@ describeRepeats('Connection State', () => { client = createClient(opts) M = client.messageStream client.debug('connecting before test >>') + await client.session.getSessionToken() await Promise.all([ - client.connect(), - client.session.getSessionToken(), ]) stream = await client.createStream({ name: uid('stream') @@ -91,39 +90,75 @@ describeRepeats('Connection State', () => { ))) }) - describe.only('autoConnect/Disconnect enabled', () => { + describe('autoConnect/Disconnect enabled', () => { beforeEach(async () => { await setupClient({ // new client with autoConnect autoConnect: true, autoDisconnect: true, }) - // don't explicitly disconnect + // don't explicitly connect }) - it('should connect on subscribe', async () => { + it('connects on subscribe, disconnects on end', async () => { const sub = await M.subscribe(stream.id) - const published = await publishTestMessages(2) - expect(client.getSubscriptions(stream.id)).toHaveLength(1) - subs.push(sub) + expect(client.connection.getState()).toBe('connected') + expect(M.count(stream.id)).toBe(1) + + const published = await publishTestMessages() + const received = [] - for await (const msg of sub) { - received.push(msg.getParsedContent()) + for await (const m of sub) { + received.push(m.getParsedContent()) if (received.length === published.length) { - expect(received).toEqual(published) + break } - break } - expect(M.count(stream.id)).toBe(0) - expect(client.getSubscriptions(stream.id)).toEqual([]) - expect(client.isConnected()).toBeFalsy() + expect(received).toEqual(published) + expect(client.connection.getState()).toBe('disconnected') + }) + + it('connects on subscribe, disconnects on end with two subs', async () => { + const [sub1, sub2] = await Promise.all([ + M.subscribe(stream.id), + M.subscribe(stream.id), + ]) + + expect(client.connection.getState()).toBe('connected') + expect(M.count(stream.id)).toBe(2) + + const published = await publishTestMessages() + + const received1 = [] + for await (const m of sub1) { + received1.push(m.getParsedContent()) + if (received1.length === published.length) { + break + } + } + + expect(received1).toEqual(published) + expect(client.connection.getState()).toBe('connected') + + const received2 = [] + for await (const m of sub2) { + received2.push(m) + if (received2.length === published.length) { + return + } + } + expect(client.connection.getState()).toBe('disconnected') + + expect(received2).toEqual(received1) }) }) describe('autoConnect disabled', () => { beforeEach(async () => { - return setupClient({ + await setupClient({ autoConnect: false, + autoDisconnect: false, }) + await client.connect() }) it('should error subscribe if client disconnected', async () => { From 9ecf069dc5839f84de0a1657994e3ccb302da13f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 27 Oct 2020 15:11:10 -0600 Subject: [PATCH 121/517] Add flakey stress-test that seems to reliably break docker env. --- test/flakey/EnvStressTest.test.js | 86 +++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 test/flakey/EnvStressTest.test.js diff --git a/test/flakey/EnvStressTest.test.js b/test/flakey/EnvStressTest.test.js new file mode 100644 index 000000000..a88d0827a --- /dev/null +++ b/test/flakey/EnvStressTest.test.js @@ -0,0 +1,86 @@ +import { pTimeout } from '../../src/utils' +import StreamrClient from '../../src' +import { fakePrivateKey, uid } from '../utils' +import config from '../integration/config' + +const TEST_REPEATS = 4 +const MAX_CONCURRENCY = 16 +const TEST_TIMEOUT = 2000 +const INC_FACTOR = 1.5 + +/* eslint-disable require-atomic-updates, no-loop-func */ + +describe('EnvStressTest', () => { + let client + + const createClient = (opts = {}) => new StreamrClient({ + autoConnect: false, + autoDisconnect: false, + ...config.clientOptions, + ...opts, + }) + + describe('Stream Creation + Deletion', () => { + const nextConcurrency = (j) => { + if (j === MAX_CONCURRENCY) { + return j + 1 + } + + return Math.min(MAX_CONCURRENCY, Math.round(j * INC_FACTOR)) + } + + for (let j = 1; j <= MAX_CONCURRENCY; j = nextConcurrency(j)) { + describe(`Create ${j} streams`, () => { + let errors = [] + beforeAll(() => { + errors = [] + }) + + afterAll(() => { + expect(errors).toEqual([]) + }) + + for (let i = 0; i < TEST_REPEATS; i++) { + test(`Test ${i + 1} of ${TEST_REPEATS}`, async () => { + const testDesc = `with concurrency ${j} for test ${i + 1}` + client = createClient({ + auth: { + privateKey: fakePrivateKey(), + }, + }) + + await pTimeout(client.session.getSessionToken(), TEST_TIMEOUT, `Timeout getting session token ${testDesc}`) + + const names = [] + for (let k = 0; k < j; k++) { + names.push(uid(`stream ${k + 1} . `)) + } + + const streams = await Promise.all(names.map((name, index) => ( + pTimeout(client.createStream({ + name, + requireSignedData: true, + requireEncryptedData: false, + }), TEST_TIMEOUT * j * 0.2, `Timeout creating stream ${index + 1} ${testDesc}`) + ))) + + streams.forEach((createdStream, index) => { + try { + expect(createdStream.id).toBeTruthy() + expect(createdStream.name).toBe(names[index]) + expect(createdStream.requireSignedData).toBe(true) + } catch (err) { + errors.push(`Error with stream ${index + 1} in ${testDesc}: ${err.message}`) + throw err + } + }) + + await Promise.all(streams.map((s, index) => ( + pTimeout(s.delete(), TEST_TIMEOUT * j * 0.2, `Timeout deleting stream ${index + 1} ${testDesc}`) + ))) + }, TEST_TIMEOUT * j * 1.2) + } + }) + } + }) +}) From d78e5c29241fdf2a5e0fd4ebb606b95221952add Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 27 Oct 2020 15:39:12 -0600 Subject: [PATCH 122/517] Use describeRepeats util for stream resends tests. --- test/integration/StreamResends.test.js | 661 ++++++++++++------------- 1 file changed, 318 insertions(+), 343 deletions(-) diff --git a/test/integration/StreamResends.test.js b/test/integration/StreamResends.test.js index 4cac0ae2c..905403999 100644 --- a/test/integration/StreamResends.test.js +++ b/test/integration/StreamResends.test.js @@ -1,7 +1,7 @@ import { ControlLayer } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' -import { uid, fakePrivateKey } from '../utils' +import { uid, fakePrivateKey, describeRepeats, collect, Msg } from '../utils' import StreamrClient from '../../src' import Connection from '../../src/Connection' import MessageStream from '../../src/subscribe' @@ -10,398 +10,373 @@ import config from './config' const { ControlMessage } = ControlLayer -let ID = 0 -const Msg = (opts) => ({ - value: `msg${ID++}`, // eslint-disable-line no-plusplus - ...opts, -}) - -async function collect(iterator, fn = () => {}) { - const received = [] - for await (const msg of iterator) { - received.push(msg.getParsedContent()) - await fn({ - msg, iterator, received, - }) - } - - return received -} - -const TEST_REPEATS = 2 - /* eslint-disable no-await-in-loop */ const WAIT_FOR_STORAGE_TIMEOUT = 6000 -describe('resends', () => { - for (let k = 0; k < TEST_REPEATS; k++) { - // eslint-disable-next-line no-loop-func - describe(`test repeat ${k + 1} of ${TEST_REPEATS}`, () => { - let expectErrors = 0 // check no errors by default - let onError = jest.fn() - let client - let stream - let published - let M - - const createClient = (opts = {}) => { - const c = new StreamrClient({ - auth: { - privateKey: fakePrivateKey(), - }, - autoConnect: false, - autoDisconnect: false, - maxRetries: 2, - ...config.clientOptions, - ...opts, +describeRepeats('resends', () => { + let expectErrors = 0 // check no errors by default + let onError = jest.fn() + let client + let stream + let published + let M + + const createClient = (opts = {}) => { + const c = new StreamrClient({ + auth: { + privateKey: fakePrivateKey(), + }, + autoConnect: false, + autoDisconnect: false, + maxRetries: 2, + ...config.clientOptions, + ...opts, + }) + c.onError = jest.fn() + c.on('error', onError) + return c + } + + async function waitForStorage({ streamId, streamPartition = 0, msg, timeout = 5000 }) { + const start = Date.now() + let last + // eslint-disable-next-line no-constant-condition + while (true) { + const duration = Date.now() - start + if (duration > timeout) { + client.debug('waitForStorage timeout %o', { + timeout, + duration + }, { + msg, + last: last.map((l) => l.content), }) - c.onError = jest.fn() - c.on('error', onError) - return c + const err = new Error(`timed out after ${duration}ms waiting for message`) + err.msg = msg + throw err } - async function waitForStorage({ streamId, streamPartition = 0, msg, timeout = 5000 }) { - const start = Date.now() - let last - // eslint-disable-next-line no-constant-condition - while (true) { - const duration = Date.now() - start - if (duration > timeout) { - client.debug('waitForStorage timeout %o', { - timeout, - duration - }, { - msg, - last: last.map((l) => l.content), - }) - const err = new Error(`timed out after ${duration}ms waiting for message`) - err.msg = msg - throw err - } + last = await client.getStreamLast({ + streamId, + streamPartition, + count: 3, + }) - last = await client.getStreamLast({ - streamId, - streamPartition, - count: 3, - }) + let found = false + for (const { content } of last) { + if (content.value === msg.value) { + found = true + break + } + } - let found = false - for (const { content } of last) { - if (content.value === msg.value) { - found = true - break - } - } + if (found) { + return + } - if (found) { - return - } + client.debug('message not found, retrying... %o', { + msg, last: last.map(({ content }) => content) + }) + await wait(500) + } + } - client.debug('message not found, retrying... %o', { - msg, last: last.map(({ content }) => content) - }) - await wait(500) - } - } + beforeAll(async () => { + // eslint-disable-next-line require-atomic-updates + client = createClient() + await client.session.getSessionToken() - beforeAll(async () => { - // eslint-disable-next-line require-atomic-updates - client = createClient() - await client.session.getSessionToken() + M = new MessageStream(client) + stream = await client.createStream({ + name: uid('stream'), + }) - M = new MessageStream(client) - stream = await client.createStream({ - name: uid('stream'), - }) + published = [] + await client.connect() + for (let i = 0; i < 5; i++) { + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + await wait(50) + } + }) + + beforeAll(async () => { + const lastMessage = published[published.length - 1] + await waitForStorage({ + msg: lastMessage, + timeout: WAIT_FOR_STORAGE_TIMEOUT, + streamId: stream.id, + }) + }, WAIT_FOR_STORAGE_TIMEOUT * 2) + + beforeEach(async () => { + await client.connect() + expectErrors = 0 + onError = jest.fn() + }) + + afterEach(async () => { + await wait() + // ensure no unexpected errors + expect(onError).toHaveBeenCalledTimes(expectErrors) + if (client) { + expect(client.onError).toHaveBeenCalledTimes(expectErrors) + } + }) + + afterEach(async () => { + await wait(500) + if (client) { + client.debug('disconnecting after test') + await client.disconnect() + } + + const openSockets = Connection.getOpen() + if (openSockets !== 0) { + throw new Error(`sockets not closed: ${openSockets}`) + } + }) + + describe('no data', () => { + let emptyStream + + it('handles nothing to resend', async () => { + emptyStream = await client.createStream({ + name: uid('stream') + }) + await wait(3000) - published = [] - await client.connect() - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - await wait(50) - } + const sub = await M.resend({ + streamId: emptyStream.id, + last: 5, }) - beforeAll(async () => { - const lastMessage = published[published.length - 1] - await waitForStorage({ - msg: lastMessage, - timeout: WAIT_FOR_STORAGE_TIMEOUT, - streamId: stream.id, - }) - }, WAIT_FOR_STORAGE_TIMEOUT * 2) + const receivedMsgs = await collect(sub) + expect(receivedMsgs).toHaveLength(0) + expect(M.count(emptyStream.id)).toBe(0) + }) - beforeEach(async () => { - await client.connect() - expectErrors = 0 - onError = jest.fn() + it('resendSubscribe with nothing to resend', async () => { + emptyStream = await client.createStream({ + name: uid('stream') }) - - afterEach(async () => { - await wait() - // ensure no unexpected errors - expect(onError).toHaveBeenCalledTimes(expectErrors) - if (client) { - expect(client.onError).toHaveBeenCalledTimes(expectErrors) - } + const sub = await M.resendSubscribe({ + streamId: emptyStream.id, + last: 5, }) - afterEach(async () => { - await wait(500) - if (client) { - client.debug('disconnecting after test') - await client.disconnect() - } + expect(M.count(emptyStream.id)).toBe(1) + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(emptyStream.id, message) - const openSockets = Connection.getOpen() - if (openSockets !== 0) { - throw new Error(`sockets not closed: ${openSockets}`) - } + const received = [] + for await (const m of sub) { + received.push(m) + wait(100) + break + } + expect(received).toHaveLength(1) + expect(M.count(emptyStream.id)).toBe(0) + }) + }) + + describe('with resend data', () => { + beforeEach(async () => { + // ensure last message is in storage + const lastMessage = published[published.length - 1] + await waitForStorage({ + msg: lastMessage, + timeout: WAIT_FOR_STORAGE_TIMEOUT, + streamId: stream.id, }) + }, WAIT_FOR_STORAGE_TIMEOUT * 1.2) - describe('no data', () => { - let emptyStream + it('requests resend', async () => { + const sub = await M.resend({ + streamId: stream.id, + last: published.length, + }) + const receivedMsgs = await collect(sub) + expect(receivedMsgs).toHaveLength(published.length) + expect(receivedMsgs).toEqual(published) + expect(M.count(stream.id)).toBe(0) + }) - it('handles nothing to resend', async () => { - emptyStream = await client.createStream({ - name: uid('stream') - }) - await wait(3000) + it('requests resend number', async () => { + const sub = await M.resend({ + streamId: stream.id, + last: 2, + }) - const sub = await M.resend({ - streamId: emptyStream.id, - last: 5, - }) + const receivedMsgs = await collect(sub) + expect(receivedMsgs).toHaveLength(2) + expect(receivedMsgs).toEqual(published.slice(-2)) + expect(M.count(stream.id)).toBe(0) + }) - const receivedMsgs = await collect(sub) - expect(receivedMsgs).toHaveLength(0) - expect(M.count(emptyStream.id)).toBe(0) - }) + it('closes stream', async () => { + const sub = await M.resend({ + streamId: stream.id, + last: published.length, + }) - it('resendSubscribe with nothing to resend', async () => { - emptyStream = await client.createStream({ - name: uid('stream') - }) - const sub = await M.resendSubscribe({ - streamId: emptyStream.id, - last: 5, - }) + const received = [] + for await (const m of sub) { + received.push(m) + } + expect(received).toHaveLength(published.length) + expect(M.count(stream.id)).toBe(0) + expect(sub.stream.readable).toBe(false) + expect(sub.stream.writable).toBe(false) + }) - expect(M.count(emptyStream.id)).toBe(1) - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(emptyStream.id, message) + describe('resendSubscribe', () => { + it('sees resends and realtime', async () => { + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, + }) - const received = [] - for await (const m of sub) { - received.push(m) - wait(100) - break + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) // should be realtime + published.push(message) + const receivedMsgs = await collect(sub, async ({ received }) => { + if (received.length === published.length) { + await wait() + await sub.return() } - expect(received).toHaveLength(1) - expect(M.count(emptyStream.id)).toBe(0) }) - }) - describe('with resend data', () => { - beforeEach(async () => { - // ensure last message is in storage - const lastMessage = published[published.length - 1] - await waitForStorage({ - msg: lastMessage, - timeout: WAIT_FOR_STORAGE_TIMEOUT, - streamId: stream.id, - }) - }, WAIT_FOR_STORAGE_TIMEOUT * 1.2) + const msgs = receivedMsgs + expect(msgs).toHaveLength(published.length) + expect(msgs).toEqual(published) + expect(M.count(stream.id)).toBe(0) + expect(sub.realtime.stream.readable).toBe(false) + expect(sub.realtime.stream.writable).toBe(false) + expect(sub.resend.stream.readable).toBe(false) + expect(sub.resend.stream.writable).toBe(false) + }) - it('requests resend', async () => { - const sub = await M.resend({ - streamId: stream.id, - last: published.length, - }) - const receivedMsgs = await collect(sub) - expect(receivedMsgs).toHaveLength(published.length) - expect(receivedMsgs).toEqual(published) - expect(M.count(stream.id)).toBe(0) + it('sees resends and realtime again', async () => { + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, }) - it('requests resend number', async () => { - const sub = await M.resend({ - streamId: stream.id, - last: 2, - }) - - const receivedMsgs = await collect(sub) - expect(receivedMsgs).toHaveLength(2) - expect(receivedMsgs).toEqual(published.slice(-2)) - expect(M.count(stream.id)).toBe(0) + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) // should be realtime + published.push(message) + await wait(500) + const receivedMsgs = await collect(sub, async ({ received }) => { + if (received.length === published.length) { + await sub.return() + } }) - it('closes stream', async () => { - const sub = await M.resend({ - streamId: stream.id, - last: published.length, - }) + const msgs = receivedMsgs + expect(msgs).toHaveLength(published.length) + expect(msgs).toEqual(published) + expect(M.count(stream.id)).toBe(0) + expect(sub.realtime.stream.readable).toBe(false) + expect(sub.realtime.stream.writable).toBe(false) + expect(sub.resend.stream.readable).toBe(false) + expect(sub.resend.stream.writable).toBe(false) + }) - const received = [] - for await (const m of sub) { - received.push(m) - } - expect(received).toHaveLength(published.length) - expect(M.count(stream.id)).toBe(0) - expect(sub.stream.readable).toBe(false) - expect(sub.stream.writable).toBe(false) + it('can return before start', async () => { + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, }) - describe('resendSubscribe', () => { - it('sees resends and realtime', async () => { - const sub = await M.resendSubscribe({ - streamId: stream.id, - last: published.length, - }) - - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) // should be realtime - published.push(message) - const receivedMsgs = await collect(sub, async ({ received }) => { - if (received.length === published.length) { - await wait() - await sub.return() - } - }) - - const msgs = receivedMsgs - expect(msgs).toHaveLength(published.length) - expect(msgs).toEqual(published) - expect(M.count(stream.id)).toBe(0) - expect(sub.realtime.stream.readable).toBe(false) - expect(sub.realtime.stream.writable).toBe(false) - expect(sub.resend.stream.readable).toBe(false) - expect(sub.resend.stream.writable).toBe(false) - }) - - it('sees resends and realtime again', async () => { - const sub = await M.resendSubscribe({ - streamId: stream.id, - last: published.length, - }) - - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) // should be realtime - published.push(message) - await wait(500) - const receivedMsgs = await collect(sub, async ({ received }) => { - if (received.length === published.length) { - await sub.return() - } - }) - - const msgs = receivedMsgs - expect(msgs).toHaveLength(published.length) - expect(msgs).toEqual(published) - expect(M.count(stream.id)).toBe(0) - expect(sub.realtime.stream.readable).toBe(false) - expect(sub.realtime.stream.writable).toBe(false) - expect(sub.resend.stream.readable).toBe(false) - expect(sub.resend.stream.writable).toBe(false) - }) + expect(M.count(stream.id)).toBe(1) + const message = Msg() - it('can return before start', async () => { - const sub = await M.resendSubscribe({ - streamId: stream.id, - last: published.length, - }) + await sub.return() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + await wait(500) + const received = [] + for await (const m of sub) { + received.push(m) + } - expect(M.count(stream.id)).toBe(1) - const message = Msg() + expect(received).toHaveLength(0) + expect(M.count(stream.id)).toBe(0) + expect(sub.realtime.stream.readable).toBe(false) + expect(sub.resend.stream.writable).toBe(false) + }) - await sub.return() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - await wait(500) - const received = [] - for await (const m of sub) { - received.push(m) - } + it('can end asynchronously', async () => { + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, + }) - expect(received).toHaveLength(0) - expect(M.count(stream.id)).toBe(0) - expect(sub.realtime.stream.readable).toBe(false) - expect(sub.resend.stream.writable).toBe(false) - }) + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + await wait(500) - it('can end asynchronously', async () => { - const sub = await M.resendSubscribe({ - streamId: stream.id, - last: published.length, - }) - - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - await wait(500) - - let t - let receivedMsgs - try { - receivedMsgs = await collect(sub, async ({ received }) => { - if (received.length === published.length) { - t = setTimeout(() => { - sub.cancel() - }) - } + let t + let receivedMsgs + try { + receivedMsgs = await collect(sub, async ({ received }) => { + if (received.length === published.length) { + t = setTimeout(() => { + sub.cancel() }) - } finally { - clearTimeout(t) } - - const msgs = receivedMsgs - expect(msgs).toHaveLength(published.length) - expect(msgs).toEqual(published) - expect(M.count(stream.id)).toBe(0) - expect(sub.realtime.stream.readable).toBe(false) - expect(sub.resend.stream.writable).toBe(false) }) + } finally { + clearTimeout(t) + } - it('can end inside resend', async () => { - const unsubscribeEvents = [] - client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { - unsubscribeEvents.push(m) - }) - const sub = await M.resendSubscribe({ - streamId: stream.id, - last: published.length, - }) - - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - await wait(500) - const END_AFTER = 3 - const receivedMsgs = await collect(sub, async ({ received }) => { - if (received.length === END_AFTER) { - await sub.cancel() - expect(unsubscribeEvents).toHaveLength(1) - } - }) - const msgs = receivedMsgs - expect(msgs).toHaveLength(END_AFTER) - expect(msgs).toEqual(published.slice(0, END_AFTER)) - expect(M.count(stream.id)).toBe(0) - expect(sub.realtime.stream.readable).toBe(false) - expect(sub.resend.stream.writable).toBe(false) - }) + const msgs = receivedMsgs + expect(msgs).toHaveLength(published.length) + expect(msgs).toEqual(published) + expect(M.count(stream.id)).toBe(0) + expect(sub.realtime.stream.readable).toBe(false) + expect(sub.resend.stream.writable).toBe(false) + }) + + it('can end inside resend', async () => { + const unsubscribeEvents = [] + client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { + unsubscribeEvents.push(m) + }) + const sub = await M.resendSubscribe({ + streamId: stream.id, + last: published.length, }) + + const message = Msg() + // eslint-disable-next-line no-await-in-loop + await client.publish(stream.id, message) + published.push(message) + await wait(500) + const END_AFTER = 3 + const receivedMsgs = await collect(sub, async ({ received }) => { + if (received.length === END_AFTER) { + await sub.cancel() + expect(unsubscribeEvents).toHaveLength(1) + } + }) + const msgs = receivedMsgs + expect(msgs).toHaveLength(END_AFTER) + expect(msgs).toEqual(published.slice(0, END_AFTER)) + expect(M.count(stream.id)).toBe(0) + expect(sub.realtime.stream.readable).toBe(false) + expect(sub.resend.stream.writable).toBe(false) }) }) - } + }) }) From 79f2ba01e4b1ce887a009e7505ef08befdbd5860 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 27 Oct 2020 17:17:59 -0600 Subject: [PATCH 123/517] Add wrappers in client methods to support legacy resend/subscribe APIs. --- src/StreamrClient.js | 75 +++++++++++---- src/subscribe/index.js | 69 ++++++++------ test/integration/Resends.test.js | 152 +++++++++++++++++++------------ test/utils.js | 55 ++++++++++- 4 files changed, 246 insertions(+), 105 deletions(-) diff --git a/src/StreamrClient.js b/src/StreamrClient.js index c31c9a753..c752e6672 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -8,6 +8,7 @@ import uniqueId from 'lodash.uniqueid' import Connection from './Connection' import Session from './Session' import { getVersionString } from './utils' +import { iteratorFinally } from './utils/iterators' import Publisher from './Publisher' import MessageStream from './subscribe' @@ -15,6 +16,19 @@ const { ControlMessage } = ControlLayer const { StreamMessage } = MessageLayer +function emitterMixin(obj) { + const emitter = new EventEmitter() + return Object.assign(obj, { + once: emitter.once.bind(emitter), + emit: emitter.emit.bind(emitter), + on: emitter.on.bind(emitter), + off: emitter.off.bind(emitter), + removeListener: emitter.removeListener.bind(emitter), + addListener: emitter.addListener.bind(emitter), + removeAllListeners: emitter.removeAllListeners.bind(emitter), + }) +} + export default class StreamrClient extends EventEmitter { constructor(options, connection) { super() @@ -107,8 +121,6 @@ export default class StreamrClient extends EventEmitter { this.publisher = new Publisher(this) this.messageStream = new MessageStream(this) - //this.subscriber = new Subscriber(this) - //this.resender = new Resender(this) this.connection.on('connected', this.onConnectionConnected) this.connection.on('disconnected', this.onConnectionDisconnected) @@ -155,10 +167,6 @@ export default class StreamrClient extends EventEmitter { _onError(err, ...args) { this.onError(err, ...args) - if (!(err instanceof Connection.ConnectionError)) { - this.debug('disconnecting due to error', err) - this.disconnect() - } } async send(request) { @@ -173,8 +181,26 @@ export default class StreamrClient extends EventEmitter { console.error(error) } - async resend(...args) { - return this.resender.resend(...args) + async resend(opts, fn) { + const task = this.messageStream.resend(opts) + if (!fn) { + return task + } + + const r = task.then((sub) => emitterMixin(sub)) + + Promise.resolve(r).then(async (sub) => { + sub.emit('resending') + for await (const msg of sub) { + await fn(msg.getParsedContent(), msg) + } + sub.emit('resent') + return sub + }).catch((err) => { + this.emit('error', err) + }) + + return r } isConnected() { @@ -207,7 +233,6 @@ export default class StreamrClient extends EventEmitter { disconnect() { this.publisher.stop() - //this.subscriber.stop() return this.connection.disconnect() } @@ -224,14 +249,28 @@ export default class StreamrClient extends EventEmitter { } async subscribe(opts, fn) { - this.emit('subscribing') - const subTask = this.messageStream.subscribe(opts) + let subTask + if (opts.resend || opts.from || opts.to || opts.last) { + subTask = this.messageStream.resendSubscribe(opts) + } else { + subTask = this.messageStream.subscribe(opts) + } + if (!fn) { - const sub = await subTask - this.emit('subscribed') - return sub + return subTask } - Promise.resolve(subTask).then(async (sub) => { + + const r = Promise.resolve(subTask).then((sub) => { + const sub2 = emitterMixin(sub) + if (!sub.resend) { return sub2 } + sub2.resend = iteratorFinally(sub.resend, () => { + sub.emit('resent') + }) + return sub2 + }) + + Promise.resolve(r).then(async (sub) => { + sub.emit('subscribed') for await (const msg of sub) { await fn(msg.getParsedContent(), msg) } @@ -239,17 +278,13 @@ export default class StreamrClient extends EventEmitter { }).catch((err) => { this.emit('error', err) }) - return subTask + return r } unsubscribe(...args) { return this.messageStream.unsubscribe(...args) } - //unsubscribeAll(...args) { - //return this.subscriber.unsubscribeAll(...args) - //} - getSubscriptions(...args) { return this.messageStream.getAll(...args) } diff --git a/src/subscribe/index.js b/src/subscribe/index.js index d7d25b2da..5d24cb60b 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -107,7 +107,11 @@ export function validateOptions(optionsOrStreamId) { } } else if (typeof optionsOrStreamId === 'object') { if (optionsOrStreamId.stream) { - return validateOptions(optionsOrStreamId.stream) + const { stream, ...other } = optionsOrStreamId + return validateOptions({ + ...other, + ...validateOptions(stream), + }) } if (optionsOrStreamId.id != null && optionsOrStreamId.streamId == null) { @@ -379,47 +383,59 @@ async function unsubscribe(client, { streamId, streamPartition = 0 }) { // Resends // -function createResendRequest({ - requestId = uuid('rs'), - streamId, - streamPartition = 0, - publisherId, - msgChainId, - sessionToken, - ...options -}) { - let request - const opts = { +function createResendRequest(resendOptions) { + const { + requestId = uuid('rs'), + streamId, + streamPartition = 0, + sessionToken, + ...options + } = resendOptions + + const { + from, + to, + last, + publisherId, + msgChainId, + } = { + ...options, + ...options.resend + } + + const commonOpts = { streamId, streamPartition, requestId, sessionToken, } - if (options.last > 0) { + let request + + if (last > 0) { request = new ResendLastRequest({ - ...opts, - numberLast: options.last, + ...commonOpts, + numberLast: last, }) - } else if (options.from && !options.to) { + } else if (from && !to) { request = new ResendFromRequest({ - ...opts, - fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), + ...commonOpts, + fromMsgRef: new MessageRef(from.timestamp, from.sequenceNumber), publisherId, msgChainId, }) - } else if (options.from && options.to) { + } else if (from && to) { request = new ResendRangeRequest({ - ...opts, - fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), - toMsgRef: new MessageRef(options.to.timestamp, options.to.sequenceNumber), + ...commonOpts, + fromMsgRef: new MessageRef(from.timestamp, from.sequenceNumber), + toMsgRef: new MessageRef(to.timestamp, to.sequenceNumber), publisherId, msgChainId, }) } if (!request) { - throw new Error("Can't _requestResend without resend options") + throw new Error(`Can't _requestResend without resend options. Got: ${JSON.stringify(resendOptions)}`) } return request } @@ -465,7 +481,8 @@ async function getResendStream(client, opts) { // wait for resend complete message or resend request done await Promise.race([ resend(client, { - requestId, ...options, + requestId, + ...options, }), onResendDone ]) @@ -718,9 +735,9 @@ export default class Subscriptions { const [, it] = CancelableGenerator((async function* ResendSubIterator() { // iterate over resend - yield* resendSub + yield* it.resend // then iterate over realtime subscription - yield* sub + yield* it.realtime }()), async (err) => { await end(err) }) diff --git a/test/integration/Resends.test.js b/test/integration/Resends.test.js index d7f2d2cc9..c4c64f751 100644 --- a/test/integration/Resends.test.js +++ b/test/integration/Resends.test.js @@ -1,53 +1,92 @@ import { wait, waitForCondition, waitForEvent } from 'streamr-test-utils' import Debug from 'debug' -import { uid } from '../utils' +import { uid, describeRepeats, fakePrivateKey, getWaitForStorage, getPublishTestMessages } from '../utils' import StreamrClient from '../../src' +import Connection from '../../src/Connection' import config from './config' -const createClient = (opts = {}) => new StreamrClient({ - apiKey: 'tester1-api-key', - autoConnect: false, - autoDisconnect: false, - ...config.clientOptions, - ...opts, -}) - const MAX_MESSAGES = 10 -const TEST_REPEATS = 10 +const WAIT_FOR_STORAGE_TIMEOUT = 6000 + +/* eslint-disable no-await-in-loop */ describe('StreamrClient resends', () => { describe('resend', () => { + let expectErrors = 0 // check no errors by default + let onError = jest.fn() + + const createClient = (opts = {}) => { + const c = new StreamrClient({ + auth: { + privateKey: fakePrivateKey(), + }, + autoConnect: false, + autoDisconnect: false, + maxRetries: 2, + ...config.clientOptions, + ...opts, + }) + c.onError = jest.fn() + c.on('error', onError) + return c + } + let client let stream - let publishedMessages + let published + let publishTestMessages beforeEach(async () => { client = createClient() await client.connect() - publishedMessages = [] + published = [] stream = await client.createStream({ name: uid('resends') }) - for (let i = 0; i < MAX_MESSAGES; i++) { - const message = { - msg: uid('message'), - } + publishTestMessages = getPublishTestMessages(client, stream.id) - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - publishedMessages.push(message) - } + published = await publishTestMessages(MAX_MESSAGES) + + const waitForStorage = getWaitForStorage(client) + const lastMessage = published[published.length - 1] + await waitForStorage({ + msg: lastMessage, + timeout: WAIT_FOR_STORAGE_TIMEOUT, + streamId: stream.id, + }) + }) - await wait(5000) // wait for messages to (hopefully) land in storage - }, 10 * 1000) + beforeEach(async () => { + await client.connect() + expectErrors = 0 + onError = jest.fn() + }) afterEach(async () => { - await client.disconnect() + await wait() + // ensure no unexpected errors + expect(onError).toHaveBeenCalledTimes(expectErrors) + if (client) { + expect(client.onError).toHaveBeenCalledTimes(expectErrors) + } + }) + + afterEach(async () => { + await wait(500) + if (client) { + client.debug('disconnecting after test') + await client.disconnect() + } + + const openSockets = Connection.getOpen() + if (openSockets !== 0) { + throw new Error(`sockets not closed: ${openSockets}`) + } }) describe('issue resend and subscribe at the same time', () => { @@ -79,7 +118,7 @@ describe('StreamrClient resends', () => { client.publish(stream.id, realtimeMessage), waitForCondition(() => realtimeMessages.length === 1, 10000) ]) - expect(resentMessages).toStrictEqual(publishedMessages) + expect(resentMessages).toStrictEqual(published) expect(realtimeMessages).toStrictEqual([realtimeMessage]) }, 18000) @@ -112,7 +151,7 @@ describe('StreamrClient resends', () => { client.publish(stream.id, realtimeMessage), waitForCondition(() => realtimeMessages.length === 1, 5000) ]) - expect(resentMessages).toStrictEqual(publishedMessages) + expect(resentMessages).toStrictEqual(published) expect(realtimeMessages).toStrictEqual([realtimeMessage]) }, 15000) @@ -144,7 +183,7 @@ describe('StreamrClient resends', () => { client.publish(stream.id, realtimeMessage), waitForCondition(() => realtimeMessages.length === 1, 5000) ]) - expect(resentMessages).toStrictEqual([...publishedMessages, realtimeMessage]) + expect(resentMessages).toStrictEqual([...published, realtimeMessage]) expect(realtimeMessages).toStrictEqual([realtimeMessage]) }, 15000) @@ -177,14 +216,14 @@ describe('StreamrClient resends', () => { client.publish(stream.id, realtimeMessage), waitForCondition(() => realtimeMessages.length === 1, 5000) ]) - expect(resentMessages).toStrictEqual([...publishedMessages, realtimeMessage]) + expect(resentMessages).toStrictEqual([...published, realtimeMessage]) expect(realtimeMessages).toStrictEqual([realtimeMessage]) }, 15000) }) - for (let i = 0; i < TEST_REPEATS; i++) { + describeRepeats('resend repeats', () => { // eslint-disable-next-line no-loop-func - it(`resend last using resend function on try ${i}`, async () => { + test('resend last using resend function', async () => { const receivedMessages = [] // eslint-disable-next-line no-await-in-loop @@ -202,17 +241,15 @@ describe('StreamrClient resends', () => { // eslint-disable-next-line no-loop-func sub.once('resent', () => { - expect(receivedMessages).toStrictEqual(publishedMessages) + expect(receivedMessages).toStrictEqual(published) }) // eslint-disable-next-line no-await-in-loop await waitForCondition(() => receivedMessages.length === MAX_MESSAGES, 10000) - }, 10000) - } + }, 10000 * 1.2) - for (let i = 0; i < TEST_REPEATS; i++) { // eslint-disable-next-line no-loop-func - it(`resend last using subscribe function on try ${i}`, async () => { + test('resend last using subscribe function', async () => { const receivedMessages = [] // eslint-disable-next-line no-await-in-loop @@ -224,12 +261,11 @@ describe('StreamrClient resends', () => { }, (message) => { receivedMessages.push(message) }) - // eslint-disable-next-line no-loop-func - await waitForEvent(sub, 'resent') - expect(receivedMessages).toStrictEqual(publishedMessages) - }, 10000) - } + await waitForEvent(sub, 'resent', 10000) + expect(receivedMessages).toStrictEqual(published) + }, 10000 * 1.2) + }) it('resend last using subscribe and publish messages after resend', async () => { const receivedMessages = [] @@ -245,7 +281,7 @@ describe('StreamrClient resends', () => { // wait for resend MAX_MESSAGES await waitForCondition(() => receivedMessages.length === MAX_MESSAGES, 20000) - expect(receivedMessages).toStrictEqual(publishedMessages) + expect(receivedMessages).toStrictEqual(published) // publish after resend, realtime subscription messages for (let i = MAX_MESSAGES; i < MAX_MESSAGES * 2; i++) { @@ -255,11 +291,11 @@ describe('StreamrClient resends', () => { // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) - publishedMessages.push(message) + published.push(message) } await waitForCondition(() => receivedMessages.length === MAX_MESSAGES * 2, 10000) - expect(receivedMessages).toStrictEqual(publishedMessages) + expect(receivedMessages).toStrictEqual(published) }, 40000) it('resend last using subscribe and publish realtime messages', async () => { @@ -275,7 +311,7 @@ describe('StreamrClient resends', () => { }) sub.once('resent', async () => { - expect(receivedMessages).toStrictEqual(publishedMessages) + expect(receivedMessages).toStrictEqual(published) expect(receivedMessages).toHaveLength(MAX_MESSAGES) for (let i = MAX_MESSAGES; i < MAX_MESSAGES * 2; i++) { const message = { @@ -284,35 +320,35 @@ describe('StreamrClient resends', () => { // eslint-disable-next-line no-await-in-loop await client.publish(stream.id, message) - publishedMessages.push(message) + published.push(message) } }) await waitForCondition(() => receivedMessages.length === MAX_MESSAGES * 2, 20000) - expect(receivedMessages).toStrictEqual(publishedMessages) + expect(receivedMessages).toStrictEqual(published) }, 40000) it('long resend', async (done) => { client.debug('disabling verbose logging') Debug.disable() - const LONG_RESEND = 10000 - const publishedMessages2 = [] stream = await client.createStream({ name: uid('resends') }) - for (let i = 0; i < LONG_RESEND; i++) { - const message = { - msg: uid('message'), - } + const LONG_RESEND = 10000 - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - publishedMessages2.push(message) - } + publishTestMessages = getPublishTestMessages(client, stream.id) + published = await publishTestMessages(LONG_RESEND) + + const waitForStorage = getWaitForStorage(client) + const lastMessage = published[published.length - 1] + await waitForStorage({ + msg: lastMessage, + timeout: 60000, + streamId: stream.id, + }) - await wait(30000) await client.disconnect() // resend from LONG_RESEND messages @@ -331,8 +367,8 @@ describe('StreamrClient resends', () => { }) sub.once('resent', () => { - expect(receivedMessages).toEqual(publishedMessages2) - expect(publishedMessages2.length).toBe(LONG_RESEND) + expect(receivedMessages).toEqual(published) + expect(published.length).toBe(LONG_RESEND) done() }) }, 300000) diff --git a/test/utils.js b/test/utils.js index 170c60637..389d923b1 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,3 +1,5 @@ +import { wait } from 'streamr-test-utils' + import { pTimeout } from '../src/utils' const crypto = require('crypto') @@ -26,7 +28,7 @@ describeRepeats.skip = (msg, fn) => { } describeRepeats.only = (msg, fn) => { - describeRepeats(fn, describe.only) + describeRepeats(msg, fn, describe.only) } export async function collect(iterator, fn = () => {}) { @@ -58,3 +60,54 @@ export function getPublishTestMessages(client, defaultStreamId) { return published } } + +export function getWaitForStorage(client) { + /* eslint-disable no-await-in-loop */ + return async ({ + streamId, + streamPartition = 0, + msg, + interval = 500, + timeout = 5000, + }) => { + const start = Date.now() + let last + // eslint-disable-next-line no-constant-condition + let found = false + while (!found) { + const duration = Date.now() - start + if (duration > timeout) { + client.debug('waitForStorage timeout %o', { + timeout, + duration + }, { + msg, + last: last.map((l) => l.content), + }) + const err = new Error(`timed out after ${duration}ms waiting for message`) + err.msg = msg + throw err + } + + last = await client.getStreamLast({ + streamId, + streamPartition, + count: 3, + }) + + for (const { content } of last) { + if (content.value === msg.value) { + found = true + return + } + } + + client.debug('message not found, retrying... %o', { + msg, last: last.map(({ content }) => content) + }) + + await wait(interval) + } + } + /* eslint-enable no-await-in-loop */ +} From 13435a8ca636a2ab73a1ca7c19fa7f7c19bfa123 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 27 Oct 2020 21:58:26 -0600 Subject: [PATCH 124/517] Integration tests passing, haphazardly. --- src/StreamrClient.js | 7 +++++-- src/subscribe/index.js | 8 +++++++- test/integration/Sequencing.test.js | 19 +++++++++++++++++-- .../integration/StreamConnectionState.test.js | 1 + test/integration/StreamEndpoints.test.js | 2 +- test/integration/Subscription.test.js | 11 ++++------- 6 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/StreamrClient.js b/src/StreamrClient.js index c752e6672..6f40a68e4 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -281,8 +281,11 @@ export default class StreamrClient extends EventEmitter { return r } - unsubscribe(...args) { - return this.messageStream.unsubscribe(...args) + async unsubscribe(opts) { + const sub = await this.messageStream.unsubscribe(opts) + if (sub && opts.emit) { + opts.emit('unsubscribed') + } } getSubscriptions(...args) { diff --git a/src/subscribe/index.js b/src/subscribe/index.js index 5d24cb60b..0e7adb8ab 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -693,13 +693,18 @@ export default class Subscriptions { } async unsubscribe(options) { + if (options && options.options) { + return this.unsubscribe(options.options) + } + const { key } = validateOptions(options) const sub = this.subscriptions.get(key) if (!sub) { - return + return Promise.resolve() } await sub.cancel() // close all streams (thus unsubscribe) + return sub } async subscribe(options) { @@ -744,6 +749,7 @@ export default class Subscriptions { // attach additional utility functions return Object.assign(it, { + options, realtime: sub, resend: resendSub, }) diff --git a/test/integration/Sequencing.test.js b/test/integration/Sequencing.test.js index 93a448eb9..9daee0958 100644 --- a/test/integration/Sequencing.test.js +++ b/test/integration/Sequencing.test.js @@ -1,6 +1,6 @@ import { wait, waitForCondition, waitForEvent } from 'streamr-test-utils' -import { uid, fakePrivateKey } from '../utils' +import { uid, fakePrivateKey, getWaitForStorage } from '../utils' import StreamrClient from '../../src' import Connection from '../../src/Connection' @@ -107,7 +107,7 @@ describe('Sequencing', () => { await waitForCondition(() => ( msgsReceieved.length === msgsPublished.length - ), 5000).catch(() => {}) // ignore, tests will fail anyway + ), 8000).catch(() => {}) // ignore, tests will fail anyway expect(msgsReceieved).toEqual(msgsPublished) }, 10000) @@ -195,6 +195,13 @@ describe('Sequencing', () => { msgsReceieved.length === msgsPublished.length ), 2000).catch(() => {}) // ignore, tests will fail anyway + const lastMessage = msgsPublished[msgsPublished.length - 1] + const waitForStorage = getWaitForStorage(client) + await waitForStorage({ + msg: lastMessage, + timeout: 6000, + streamId: stream.id, + }) const msgsResent = [] const sub = await client.resend({ stream: stream.id, @@ -258,6 +265,14 @@ describe('Sequencing', () => { msgsReceieved.length === msgsPublished.length ), 2000).catch(() => {}) // ignore, tests will fail anyway + const lastMessage = msgsPublished[msgsPublished.length - 1] + const waitForStorage = getWaitForStorage(client) + await waitForStorage({ + msg: lastMessage, + timeout: 6000, + streamId: stream.id, + }) + const msgsResent = [] const sub = await client.resend({ stream: stream.id, diff --git a/test/integration/StreamConnectionState.test.js b/test/integration/StreamConnectionState.test.js index 2cf8b9509..074f47846 100644 --- a/test/integration/StreamConnectionState.test.js +++ b/test/integration/StreamConnectionState.test.js @@ -245,6 +245,7 @@ describeRepeats('Connection State', () => { } expect(received).toEqual([]) client.connect() // no await, should be ok + await wait(1000) const sub2 = await M.subscribe(stream.id) subs.push(sub) const published2 = await publishTestMessages(2) diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.js index 0aedd4dfa..86f9805eb 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.js @@ -179,7 +179,7 @@ describe('StreamEndpoints', () => { }) }) - describe('Stream configuration', () => { + describe.skip('Stream configuration', () => { it('Stream.detectFields', async () => { await client.connect() await client.publish(createdStream.id, { diff --git a/test/integration/Subscription.test.js b/test/integration/Subscription.test.js index 7f9284fb6..44d69e89b 100644 --- a/test/integration/Subscription.test.js +++ b/test/integration/Subscription.test.js @@ -48,9 +48,7 @@ describe('Subscription', () => { events.push(message) }) subscription.on('subscribed', () => events.push('subscribed')) - subscription.on('resending', () => events.push('resending')) subscription.on('resent', () => events.push('resent')) - subscription.on('no_resend', () => events.push('no_resend')) subscription.on('unsubscribed', () => events.push('unsubscribed')) subscription.on('error', () => events.push('error')) return events @@ -89,10 +87,10 @@ describe('Subscription', () => { describe('subscribe/unsubscribe events', () => { it('fires events in correct order 1', async () => { const subscriptionEvents = await createMonitoredSubscription() - await waitForEvent(subscription, 'no_resend') + await waitForEvent(subscription, 'resent') await client.unsubscribe(subscription) expect(subscriptionEvents).toEqual([ - 'no_resend', + 'resent', 'unsubscribed', ]) }) @@ -101,9 +99,9 @@ describe('Subscription', () => { describe('resending/no_resend events', () => { it('fires events in correct order 2', async () => { const subscriptionEvents = await createMonitoredSubscription() - await waitForEvent(subscription, 'no_resend') + await waitForEvent(subscription, 'resent') expect(subscriptionEvents).toEqual([ - 'no_resend', + 'resent', ]) }) }) @@ -117,7 +115,6 @@ describe('Subscription', () => { await waitForEvent(subscription, 'resent') await wait(500) // wait in case messages appear after resent event expect(subscriptionEvents).toEqual([ - 'resending', message1, message2, 'resent', From 280e073e6f5d0385e167cdbb85c530e9fb10faa3 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 29 Oct 2020 13:07:28 -0600 Subject: [PATCH 125/517] Tweak EnvStressTest concurrency + repeats. --- test/flakey/EnvStressTest.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/flakey/EnvStressTest.test.js b/test/flakey/EnvStressTest.test.js index a88d0827a..71751e081 100644 --- a/test/flakey/EnvStressTest.test.js +++ b/test/flakey/EnvStressTest.test.js @@ -3,8 +3,8 @@ import StreamrClient from '../../src' import { fakePrivateKey, uid } from '../utils' import config from '../integration/config' -const TEST_REPEATS = 4 -const MAX_CONCURRENCY = 16 +const TEST_REPEATS = 6 +const MAX_CONCURRENCY = 24 const TEST_TIMEOUT = 2000 const INC_FACTOR = 1.5 From 43790d2612063821dfe5b18fc9ad4fabd98776bb Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Sat, 31 Oct 2020 12:55:39 -0600 Subject: [PATCH 126/517] Move around new files, clean up old code. Migrate errors into files that use them. --- .eslintignore | 1 + src/AbstractSubscription.js | 203 -------- src/CombinedSubscription.js | 161 ------- src/Connection.js | 2 +- src/HistoricalSubscription.js | 72 --- src/RealTimeSubscription.js | 71 --- src/ResendUtil.js | 49 -- src/Resender.js | 182 -------- src/StreamrClient.js | 139 +++--- src/SubscribedStreamPartition.js | 113 ----- src/Subscriber.js | 433 ------------------ src/Subscription.js | 110 ----- src/errors/AuthFetchError.js | 13 - src/errors/FailedToPublishError.js | 11 - src/errors/InvalidGroupKeyError.js | 8 - src/errors/InvalidGroupKeyRequestError.js | 8 - src/errors/InvalidGroupKeyResponseError.js | 8 - src/errors/InvalidMessageTypeError.js | 8 - src/errors/SignatureRequiredError.js | 15 - src/errors/UnableToDecryptError.js | 6 - src/errors/VerificationFailedError.js | 10 - src/{ => publish}/Signer.js | 0 src/{Publisher.js => publish/index.js} | 18 +- src/rest/LoginEndpoints.js | 3 +- src/rest/authFetch.js | 15 +- src/subscribe/index.js | 23 +- src/utils/iterators.js | 92 +++- test/integration/StreamrClient.test.js | 2 +- .../{Stream.test.js => Subscriber.test.js} | 0 ...ends.test.js => SubscriberResends.test.js} | 123 ++--- .../CombinedSubscription.test.js | 0 .../HistoricalSubscription.test.js | 1 + .../MessageCreationUtil.test.js | 0 .../RealTimeSubscription.test.js | 0 test/{unit => legacy}/StreamrClient.test.js | 0 .../SubscribedStreamPartition.test.js | 0 test/unit/Signer.test.js | 2 +- test/unit/iterators.test.js | 16 +- 38 files changed, 257 insertions(+), 1661 deletions(-) delete mode 100644 src/AbstractSubscription.js delete mode 100644 src/CombinedSubscription.js delete mode 100644 src/HistoricalSubscription.js delete mode 100644 src/RealTimeSubscription.js delete mode 100644 src/ResendUtil.js delete mode 100644 src/Resender.js delete mode 100644 src/SubscribedStreamPartition.js delete mode 100644 src/Subscriber.js delete mode 100644 src/Subscription.js delete mode 100644 src/errors/AuthFetchError.js delete mode 100644 src/errors/FailedToPublishError.js delete mode 100644 src/errors/InvalidGroupKeyError.js delete mode 100644 src/errors/InvalidGroupKeyRequestError.js delete mode 100644 src/errors/InvalidGroupKeyResponseError.js delete mode 100644 src/errors/InvalidMessageTypeError.js delete mode 100644 src/errors/SignatureRequiredError.js delete mode 100644 src/errors/UnableToDecryptError.js delete mode 100644 src/errors/VerificationFailedError.js rename src/{ => publish}/Signer.js (100%) rename src/{Publisher.js => publish/index.js} (96%) rename test/integration/{Stream.test.js => Subscriber.test.js} (100%) rename test/integration/{StreamResends.test.js => SubscriberResends.test.js} (77%) rename test/{unit => legacy}/CombinedSubscription.test.js (100%) rename test/{unit => legacy}/HistoricalSubscription.test.js (99%) rename test/{unit => legacy}/MessageCreationUtil.test.js (100%) rename test/{unit => legacy}/RealTimeSubscription.test.js (100%) rename test/{unit => legacy}/StreamrClient.test.js (100%) rename test/{unit => legacy}/SubscribedStreamPartition.test.js (100%) diff --git a/.eslintignore b/.eslintignore index b381af368..afab6d78c 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,5 +2,6 @@ node_modules/** examples/** coverage/** dist/** +test/legacy/** src/shim/** test/unit/StubbedStreamrClient.js diff --git a/src/AbstractSubscription.js b/src/AbstractSubscription.js deleted file mode 100644 index 94878579d..000000000 --- a/src/AbstractSubscription.js +++ /dev/null @@ -1,203 +0,0 @@ -import { Errors, Utils } from 'streamr-client-protocol' - -import Subscription from './Subscription' - -const { OrderingUtil } = Utils - -export default class AbstractSubscription extends Subscription { - constructor({ - streamId, - streamPartition, - callback, - propagationTimeout, - resendTimeout, - orderMessages = true, - debug, - }) { - super({ - streamId, - streamPartition, - callback, - propagationTimeout, - resendTimeout, - debug, - }) - this.pendingResendRequestIds = {} - this._lastMessageHandlerPromise = {} - this.orderingUtil = (orderMessages) ? new OrderingUtil(streamId, streamPartition, (orderedMessage) => { - this._inOrderHandler(orderedMessage) - }, (from, to, publisherId, msgChainId) => { - this.emit('gap', from, to, publisherId, msgChainId) - }, this.propagationTimeout, this.resendTimeout) : undefined - - /** * Message handlers ** */ - - this.on('unsubscribed', () => { - this._clearGaps() - this.setResending(false) - }) - - this.on('disconnected', () => { - this.setState(Subscription.State.unsubscribed) - this._clearGaps() - this.setResending(false) - }) - - this.on('error', (error) => { - this._clearGaps() - this.onError(error) - }) - } - - /** - * Override to control output - */ - - onError(error) { // eslint-disable-line class-methods-use-this - console.error(error) - } - - _inOrderHandler(orderedMessage) { - this.callback(orderedMessage.getParsedContent(), orderedMessage) - if (orderedMessage.isByeMessage()) { - this.emit('done') - } - } - - addPendingResendRequestId(requestId) { - this.pendingResendRequestIds[requestId] = true - } - - async handleResentMessage(msg, requestId, verifyFn) { - this._lastMessageHandlerPromise[requestId] = this._catchAndEmitErrors(async () => { - if (!this.isResending()) { - throw new Error(`There is no resend in progress, but received resent message ${msg.serialize()}`) - } else { - await this._handleMessage(msg, verifyFn) - } - }) - return this._lastMessageHandlerPromise[requestId] - } - - async handleResending(response) { - return this._catchAndEmitErrors(() => { - if (!this.pendingResendRequestIds[response.requestId]) { - throw new Error(`Received unexpected ResendResponseResending message ${response.serialize()}`) - } - this.emit('resending', response) - }) - } - - async handleResent(response) { - return this._catchAndEmitErrors(async () => { - if (!this.pendingResendRequestIds[response.requestId]) { - throw new Error(`Received unexpected ResendResponseResent message ${response.serialize()}`) - } - - if (!this._lastMessageHandlerPromise[response.requestId]) { - throw new Error('Attempting to handle ResendResponseResent, but no messages have been received!') - } - - // Delay event emission until the last message in the resend has been handled - await this._lastMessageHandlerPromise[response.requestId] - try { - this.emit('resent', response) - } finally { - this.cleanupResponse(response) - } - }) - } - - async handleNoResend(response) { - return this._catchAndEmitErrors(async () => { - if (!this.pendingResendRequestIds[response.requestId]) { - throw new Error(`Received unexpected ResendResponseNoResend message ${response.serialize()}`) - } - try { - this.emit('no_resend', response) - } finally { - this.cleanupResponse(response) - } - }) - } - - cleanupResponse(response) { - delete this.pendingResendRequestIds[response.requestId] - delete this._lastMessageHandlerPromise[response.requestId] - if (Object.keys(this.pendingResendRequestIds).length === 0) { - this.finishResend() - } - } - - _clearGaps() { - if (this.orderingUtil) { - this.orderingUtil.clearGaps() - } - } - - stop() { - this._clearGaps() - } - - getState() { - return this.state - } - - setState(state) { - this.debug(`Subscription: Stream ${this.streamId} state changed ${this.state} => ${state}`) - this.state = state - this.emit(state) - } - - handleError(err) { - /** - * If parsing the (expected) message failed, we should still mark it as received. Otherwise the - * gap detection will think a message was lost, and re-request the failing message. - */ - if (err instanceof Errors.InvalidJsonError && err.streamMessage && this.orderingUtil) { - this.orderingUtil.markMessageExplicitly(err.streamMessage) - } - this.emit('error', err) - } - - async _catchAndEmitErrors(fn) { - try { - return await fn() - } catch (err) { - this.emit('error', err) - // Swallow rejection - return Promise.resolve() - } - } - - /** - * Ensures validations resolve in order that they were triggered - */ - - async _queuedValidate(msg, verifyFn) { - // wait for previous validation (if any) - const queue = Promise.all([ - this.validationQueue, - // kick off job in parallel - verifyFn(msg), - ]).then((value) => { - this.validationQueue = null // clean up (allow gc) - return value - }, (err) => { - this.validationQueue = null // clean up (allow gc) - throw err - }) - this.validationQueue = queue - return queue - } - - async _handleMessage(msg, verifyFn) { - await this._queuedValidate(msg, verifyFn) - this.emit('message received') - if (this.orderingUtil) { - this.orderingUtil.add(msg) - } else { - this._inOrderHandler(msg) - } - } -} diff --git a/src/CombinedSubscription.js b/src/CombinedSubscription.js deleted file mode 100644 index 8dbc426b5..000000000 --- a/src/CombinedSubscription.js +++ /dev/null @@ -1,161 +0,0 @@ -import HistoricalSubscription from './HistoricalSubscription' -import RealTimeSubscription from './RealTimeSubscription' -import Subscription from './Subscription' - -export default class CombinedSubscription extends Subscription { - constructor({ - streamId, - streamPartition, - callback, - options, - propagationTimeout, - resendTimeout, - orderMessages = true, - debug, - }) { - super({ - streamId, - streamPartition, - callback, - propagationTimeout, - resendTimeout, - debug, - }) - - this.sub = new HistoricalSubscription({ - streamId, - streamPartition, - callback, - options, - propagationTimeout: this.propagationTimeout, - resendTimeout: this.resendTimeout, - orderMessages, - debug: this.debug, - }) - this.realTimeMsgsQueue = [] - this.sub.on('message received', (msg) => { - if (msg) { - this.realTimeMsgsQueue.push(msg) - } - }) - this.sub.on('initial_resend_done', async () => { - this._unbindListeners(this.sub) - const realTime = new RealTimeSubscription({ - streamId, - streamPartition, - callback, - propagationTimeout: this.propagationTimeout, - resendTimeout: this.resendTimeout, - orderMessages, - debug: this.debug, - }) - this._bindListeners(realTime) - if (this.sub.orderingUtil) { - realTime.orderingUtil.orderedChains = this.sub.orderingUtil.orderedChains - Object.keys(this.sub.orderingUtil.orderedChains).forEach((key) => { - realTime.orderingUtil.orderedChains[key].inOrderHandler = realTime.orderingUtil.inOrderHandler - realTime.orderingUtil.orderedChains[key].gapHandler = realTime.orderingUtil.gapHandler - }) - } - await Promise.all(this.realTimeMsgsQueue.map((msg) => realTime.handleBroadcastMessage(msg, () => true))) - this.realTimeMsgsQueue = [] - this.sub = realTime - }) - this._bindListeners(this.sub) - } - - _bindListeners(sub) { - sub.on('done', () => this.emit('done')) - sub.on('gap', (from, to, publisherId, msgChainId) => this.emit('gap', from, to, publisherId, msgChainId)) - sub.on('error', (err) => this.emit('error', err)) - sub.on('resending', (response) => this.emit('resending', response)) - sub.on('resent', (response) => this.emit('resent', response)) - sub.on('no_resend', (response) => this.emit('no_resend', response)) - sub.on('initial_resend_done', (response) => this.emit('initial_resend_done', response)) - sub.on('message received', () => this.emit('message received')) - - // hack to ensure inner subscription state is reflected in the outer subscription state - // restore in _unbindListeners - // still not foolproof though - /* eslint-disable no-param-reassign */ - sub.setState = this.setState.bind(this) - sub.getState = this.getState.bind(this) - /* eslint-enable no-param-reassign */ - } - - _unbindListeners(sub) { - this.sub.removeAllListeners() - - // delete to (probably) restore original (prototype) methods - /* eslint-disable no-param-reassign */ - if (Object.hasOwnProperty.call(sub, 'setState')) { - delete sub.setState - } - - if (Object.hasOwnProperty.call(sub, 'getState')) { - delete sub.getState - } - /* eslint-enable no-param-reassign */ - } - - stop() { - return this.sub.stop() - } - - addPendingResendRequestId(requestId) { - this.sub.addPendingResendRequestId(requestId) - } - - async handleResentMessage(msg, requestId, verifyFn) { - return this.sub.handleResentMessage(msg, requestId, verifyFn) - } - - async handleResending(response) { - return this.sub.handleResending(response) - } - - async handleResent(response) { - return this.sub.handleResent(response) - } - - async handleNoResend(response) { - return this.sub.handleNoResend(response) - } - - async handleBroadcastMessage(msg, verifyFn) { - return this.sub.handleBroadcastMessage(msg, verifyFn) - } - - finishResend() { - return this.sub.finishResend() - } - - hasResendOptions() { - return this.sub.hasResendOptions() - } - - getResendOptions() { - return this.sub.getResendOptions() - } - - setResending(resending) { - return this.sub.setResending(resending) - } - - setState(state) { - this.sub.state = state - super.setState(state) - } - - handleError(err) { - return this.sub.handleError(err) - } - - onDisconnected() { - this.sub.onDisconnected() - } - - isResending() { - return this.sub.isResending() - } -} diff --git a/src/Connection.js b/src/Connection.js index 730df4f70..fdf30f40b 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -35,7 +35,7 @@ async function OpenWebSocket(url, ...args) { if (!url) { throw new ConnectionError('URL is not defined!') } - const socket = new WebSocket(url, ...args) + const socket = new WebSocket(url) socket.id = uniqueId('socket') socket.binaryType = 'arraybuffer' let opened = 0 diff --git a/src/HistoricalSubscription.js b/src/HistoricalSubscription.js deleted file mode 100644 index 86efc5d16..000000000 --- a/src/HistoricalSubscription.js +++ /dev/null @@ -1,72 +0,0 @@ -import AbstractSubscription from './AbstractSubscription' - -export default class HistoricalSubscription extends AbstractSubscription { - constructor({ - streamId, - streamPartition, - callback, - options, - propagationTimeout, - resendTimeout, - orderMessages = true, - debug - }) { - super({ - streamId, - streamPartition, - callback, - propagationTimeout, - resendTimeout, - orderMessages, - debug, - }) - this.resendOptions = options - if (!this.resendOptions || (!this.resendOptions.from && !this.resendOptions.last)) { - throw new Error('Resend options (either "from", "from" and "to", or "last") must be defined in a historical subscription.') - } - - if (this.resendOptions.from != null && this.resendOptions.last != null) { - throw new Error(`Multiple resend options active! Please use only one: ${JSON.stringify(this.resendOptions)}`) - } - - if (this.resendOptions.msgChainId != null && typeof this.resendOptions.publisherId === 'undefined') { - throw new Error('publisherId must be defined as well if msgChainId is defined.') - } - - if (this.resendOptions.from == null && this.resendOptions.to != null) { - throw new Error('"from" must be defined as well if "to" is defined.') - } - } - - async handleBroadcastMessage(msg, verifyFn) { - await this._queuedValidate(msg, verifyFn) - this.emit('message received', msg) - } - - /* eslint-disable class-methods-use-this */ - hasResendOptions() { - return true - } - - isResending() { - return true - } - - setResending() {} - /* eslint-enable class-methods-use-this */ - - getResendOptions() { - return this.resendOptions - } - - finishResend() { - this._lastMessageHandlerPromise = null - if (Object.keys(this.pendingResendRequestIds).length === 0) { - this.emit('initial_resend_done') - } - } - - /* eslint-disable class-methods-use-this */ - onDisconnected() {} - /* eslint-enable class-methods-use-this */ -} diff --git a/src/RealTimeSubscription.js b/src/RealTimeSubscription.js deleted file mode 100644 index d2db47514..000000000 --- a/src/RealTimeSubscription.js +++ /dev/null @@ -1,71 +0,0 @@ -import debugFactory from 'debug' -import uniqueId from 'lodash.uniqueid' - -import Subscription from './Subscription' -import AbstractSubscription from './AbstractSubscription' - -export default class RealTimeSubscription extends AbstractSubscription { - constructor({ - streamId, - streamPartition, - callback, - propagationTimeout, - resendTimeout, - orderMessages = true, - debug, - }) { - super({ - streamId, - streamPartition, - callback, - propagationTimeout, - resendTimeout, - orderMessages, - debug, - }) - - const id = uniqueId('Subscription') - if (debug) { - this.debug = debug.extend(id) - } else { - this.debug = debugFactory(`StreamrClient::${id}`) - } - - this.resending = false - } - - // All the handle* methods should: - // - return a promise for consistency - // - swallow exceptions and emit them as 'error' events - - async handleBroadcastMessage(msg, verifyFn) { - return this._catchAndEmitErrors(() => this._handleMessage(msg, verifyFn)) - } - - finishResend() { - this.setResending(false) - } - - /* eslint-disable class-methods-use-this */ - hasResendOptions() { - return false - } - - getResendOptions() { - return {} - } - /* eslint-enable class-methods-use-this */ - - isResending() { - return this.resending - } - - setResending(resending) { - this.debug(`Subscription: Stream ${this.streamId} resending: ${resending}`) - this.resending = resending - } - - onDisconnected() { - this.setState(Subscription.State.unsubscribed) - } -} diff --git a/src/ResendUtil.js b/src/ResendUtil.js deleted file mode 100644 index 938ec4e0f..000000000 --- a/src/ResendUtil.js +++ /dev/null @@ -1,49 +0,0 @@ -import EventEmitter from 'eventemitter3' -import { ControlLayer } from 'streamr-client-protocol' -import Debug from 'debug' - -import { uuid } from './utils' - -const { ControlMessage } = ControlLayer - -export default class ResendUtil extends EventEmitter { - constructor({ debug } = {}) { - super() - this.debug = debug ? debug.extend('Util') : Debug('ResendUtil') - this.subForRequestId = {} - } - - /* eslint-disable-next-line class-methods-use-this */ - generateRequestId() { - return uuid('r') - } - - _subForRequestIdExists(requestId) { - return requestId in this.subForRequestId - } - - getSubFromResendResponse(response) { - if (!this._subForRequestIdExists(response.requestId)) { - const error = new Error(`Received unexpected ${response.constructor.name} message ${response.serialize()}`) - this.emit('error', error) - } - - return this.subForRequestId[response.requestId] - } - - deleteDoneSubsByResponse(response) { - // TODO: replace with response.requestId - if (response.type === ControlMessage.TYPES.ResendResponseResent || response.type === ControlMessage.TYPES.ResendResponseNoResend) { - this.debug('remove', response.requestId) - delete this.subForRequestId[response.requestId] - } - } - - registerResendRequestForSub(sub) { - const requestId = this.generateRequestId() - this.debug('add', requestId) - this.subForRequestId[requestId] = sub - sub.addPendingResendRequestId(requestId) - return requestId - } -} diff --git a/src/Resender.js b/src/Resender.js deleted file mode 100644 index 93342b067..000000000 --- a/src/Resender.js +++ /dev/null @@ -1,182 +0,0 @@ -import once from 'once' -import { ControlLayer, MessageLayer } from 'streamr-client-protocol' - -import HistoricalSubscription from './HistoricalSubscription' -import Subscription from './Subscription' -import ResendUtil from './ResendUtil' - -const { ResendLastRequest, ResendFromRequest, ResendRangeRequest, ControlMessage } = ControlLayer - -const { MessageRef } = MessageLayer - -export default class Resender { - constructor(client) { - this.client = client - this.debug = client.debug.extend('Resends') - this.onErrorEmit = this.client.getErrorEmitter(this) - - this.resendUtil = new ResendUtil({ - debug: this.debug, - }) - - this.resendUtil.on('error', this.onErrorEmit) - - // Unicast messages to a specific subscription only - this.onUnicastMessage = this.onUnicastMessage.bind(this) - this.client.connection.on(ControlMessage.TYPES.UnicastMessage, this.onUnicastMessage) - - // Route resending state messages to corresponding Subscriptions - this.onResendResponseResending = this.onResendResponseResending.bind(this) - this.client.connection.on(ControlMessage.TYPES.ResendResponseResending, this.onResendResponseResending) - - this.onResendResponseNoResend = this.onResendResponseNoResend.bind(this) - this.client.connection.on(ControlMessage.TYPES.ResendResponseNoResend, this.onResendResponseNoResend) - - this.onResendResponseResent = this.onResendResponseResent.bind(this) - this.client.connection.on(ControlMessage.TYPES.ResendResponseResent, this.onResendResponseResent) - } - - onResendResponseResent(response) { - // eslint-disable-next-line no-underscore-dangle - const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) - const sub = this.resendUtil.getSubFromResendResponse(response) - this.resendUtil.deleteDoneSubsByResponse(response) - - if (!stream || !sub || !stream.getSubscription(sub.id)) { - this.debug('resent: Subscription %s is gone already', response.requestId) - return - } - stream.getSubscription(sub.id).handleResent(response) - } - - onResendResponseNoResend(response) { - // eslint-disable-next-line no-underscore-dangle - const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) - const sub = this.resendUtil.getSubFromResendResponse(response) - this.resendUtil.deleteDoneSubsByResponse(response) - - if (!stream || !sub || !stream.getSubscription(sub.id)) { - this.debug('resent: Subscription %s is gone already', response.requestId) - return - } - - stream.getSubscription(sub.id).handleNoResend(response) - } - - onResendResponseResending(response) { - // eslint-disable-next-line no-underscore-dangle - const stream = this.client.subscriber._getSubscribedStreamPartition(response.streamId, response.streamPartition) - const sub = this.resendUtil.getSubFromResendResponse(response) - - if (!stream || !sub || !stream.getSubscription(sub.id)) { - this.debug('resent: Subscription %s is gone already', response.requestId) - return - } - stream.getSubscription(sub.id).handleResending(response) - } - - async onUnicastMessage(msg) { - // eslint-disable-next-line no-underscore-dangle - const stream = this.client.subscriber._getSubscribedStreamPartition( - msg.streamMessage.getStreamId(), - msg.streamMessage.getStreamPartition() - ) - - if (!stream) { - this.debug('WARN: message received for stream with no subscriptions: %s', msg.streamMessage.getStreamId()) - return - } - - const sub = this.resendUtil.getSubFromResendResponse(msg) - - if (!sub || !stream.getSubscription(sub.id)) { - this.debug('WARN: request id not found for stream: %s, sub: %s', msg.streamMessage.getStreamId(), msg.requestId) - return - } - // sub.handleResentMessage never rejects: on any error it emits an 'error' event on the Subscription - sub.handleResentMessage( - msg.streamMessage, msg.requestId, - once(() => stream.verifyStreamMessage(msg.streamMessage)), // ensure verification occurs only once - ) - } - - async resend(optionsOrStreamId, callback) { - // eslint-disable-next-line no-underscore-dangle - const options = this.client.subscriber._validateParameters(optionsOrStreamId, callback) - - if (!options.stream) { - throw new Error('resend: Invalid arguments: options.stream is not given') - } - - if (!options.resend) { - throw new Error('resend: Invalid arguments: options.resend is not given') - } - - const sub = new HistoricalSubscription({ - streamId: options.stream, - streamPartition: options.partition || 0, - callback, - options: options.resend, - propagationTimeout: this.client.options.gapFillTimeout, - resendTimeout: this.client.options.retryResendAfter, - orderMessages: this.client.orderMessages, - debug: this.debug, - }) - - // eslint-disable-next-line no-underscore-dangle - this.client.subscriber._addSubscription(sub) - // TODO remove _addSubscription after uncoupling Subscription and Resend - sub.setState(Subscription.State.subscribed) - // eslint-disable-next-line no-underscore-dangle - sub.once('initial_resend_done', () => this.client.subscriber._removeSubscription(sub)) - await this._requestResend(sub).catch((err) => { - this.onErrorEmit(err) - throw err - }) - return sub - } - - async _requestResend(sub, options = sub.getResendOptions()) { - sub.setResending(true) - const requestId = this.resendUtil.registerResendRequestForSub(sub) - const sessionToken = await this.client.session.getSessionToken() - let request - if (options.last > 0) { - request = new ResendLastRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId, - numberLast: options.last, - sessionToken, - }) - } else if (options.from && !options.to) { - request = new ResendFromRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId, - fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), - publisherId: options.publisherId, - msgChainId: options.msgChainId, - sessionToken, - }) - } else if (options.from && options.to) { - request = new ResendRangeRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId, - fromMsgRef: new MessageRef(options.from.timestamp, options.from.sequenceNumber), - toMsgRef: new MessageRef(options.to.timestamp, options.to.sequenceNumber), - publisherId: options.publisherId, - msgChainId: options.msgChainId, - sessionToken, - }) - } - - if (!request) { - throw new Error("Can't _requestResend without resend options") - } - - this.debug('_requestResend: %o', request) - await this.client.send(request) - } -} diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 6f40a68e4..f64fab5d2 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -1,16 +1,16 @@ -import EventEmitter from 'eventemitter3' -import debugFactory from 'debug' import qs from 'qs' -import { Wallet } from 'ethers' -import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' +import EventEmitter from 'eventemitter3' import uniqueId from 'lodash.uniqueid' +import { Wallet } from 'ethers' +import { ControlLayer, MessageLayer } from 'streamr-client-protocol' +import Debug from 'debug' -import Connection from './Connection' -import Session from './Session' import { getVersionString } from './utils' import { iteratorFinally } from './utils/iterators' -import Publisher from './Publisher' -import MessageStream from './subscribe' +import Connection from './Connection' +import Session from './Session' +import Publisher from './publish' +import Subscriber from './subscribe' const { ControlMessage } = ControlLayer @@ -33,7 +33,7 @@ export default class StreamrClient extends EventEmitter { constructor(options, connection) { super() this.id = uniqueId('StreamrClient') - this.debug = debugFactory(this.id) + this.debug = Debug(this.id) // Default options this.options = { debug: this.debug, @@ -94,39 +94,42 @@ export default class StreamrClient extends EventEmitter { this.getUserInfo = this.getUserInfo.bind(this) this.onConnectionConnected = this.onConnectionConnected.bind(this) this.onConnectionDisconnected = this.onConnectionDisconnected.bind(this) - this._onError = this._onError.bind(this) this.onErrorResponse = this.onErrorResponse.bind(this) this.onConnectionError = this.onConnectionError.bind(this) this.getErrorEmitter = this.getErrorEmitter.bind(this) + this.onConnectionMessage = this.onConnectionMessage.bind(this) this.on('error', this._onError) // attach before creating sub-components incase they fire error events this.session = new Session(this, this.options.auth) this.connection = connection || new Connection(this.options) - this.connection.on('message', (messageEvent) => { - if (!this.connection.isConnected()) { return } // ignore messages if not connected - let controlMessage - try { - controlMessage = ControlLayer.ControlMessage.deserialize(messageEvent.data) - } catch (err) { - this.connection.debug('<< %o', messageEvent && messageEvent.data) - this.debug('deserialize error', err) - this.emit('error', err) - return - } - this.connection.debug('<< %o', controlMessage) - this.connection.emit(controlMessage.type, controlMessage) - }) + + this.connection + .on('message', this.onConnectionMessage) + .on('connected', this.onConnectionConnected) + .on('disconnected', this.onConnectionDisconnected) + .on('error', this.onConnectionError) + .on(ControlMessage.TYPES.ErrorResponse, this.onErrorResponse) this.publisher = new Publisher(this) - this.messageStream = new MessageStream(this) + this.subscriber = new Subscriber(this) + } - this.connection.on('connected', this.onConnectionConnected) - this.connection.on('disconnected', this.onConnectionDisconnected) - this.connection.on('error', this.onConnectionError) + onConnectionMessage(messageEvent) { + if (!this.connection.isConnected()) { return } // ignore messages if not connected + let controlMessage + try { + controlMessage = ControlLayer.ControlMessage.deserialize(messageEvent.data) + } catch (err) { + this.connection.debug('<< %o', messageEvent && messageEvent.data) + this.debug('deserialize error', err) + this.emit('error', err) + return + } - this.connection.on(ControlMessage.TYPES.ErrorResponse, this.onErrorResponse) + this.connection.debug('<< %o', controlMessage) + this.connection.emit(controlMessage.type, controlMessage) } async onConnectionConnected() { @@ -140,13 +143,7 @@ export default class StreamrClient extends EventEmitter { } onConnectionError(err) { - // If there is an error parsing a json message in a stream, fire error events on the relevant subs - if ((err instanceof Errors.InvalidJsonError || err.reason instanceof Errors.InvalidJsonError)) { - //this.subscriber.onErrorMessage(err) - } else { - // if it looks like an error emit as-is, otherwise wrap in new Error - this.emit('error', new Connection.ConnectionError(err)) - } + this.emit('error', new Connection.ConnectionError(err)) } getErrorEmitter(source) { @@ -181,28 +178,6 @@ export default class StreamrClient extends EventEmitter { console.error(error) } - async resend(opts, fn) { - const task = this.messageStream.resend(opts) - if (!fn) { - return task - } - - const r = task.then((sub) => emitterMixin(sub)) - - Promise.resolve(r).then(async (sub) => { - sub.emit('resending') - for await (const msg of sub) { - await fn(msg.getParsedContent(), msg) - } - sub.emit('resent') - return sub - }).catch((err) => { - this.emit('error', err) - }) - - return r - } - isConnected() { return this.connection.isConnected() } @@ -236,6 +211,18 @@ export default class StreamrClient extends EventEmitter { return this.connection.disconnect() } + getSubscriptions(...args) { + return this.subscriber.getAll(...args) + } + + async ensureConnected() { + return this.connect() + } + + async ensureDisconnected() { + return this.disconnect() + } + logout() { return this.session.logout() } @@ -251,15 +238,15 @@ export default class StreamrClient extends EventEmitter { async subscribe(opts, fn) { let subTask if (opts.resend || opts.from || opts.to || opts.last) { - subTask = this.messageStream.resendSubscribe(opts) + subTask = this.subscriber.resendSubscribe(opts) } else { - subTask = this.messageStream.subscribe(opts) + subTask = this.subscriber.subscribe(opts) } if (!fn) { return subTask } - + // legacy event wrapper const r = Promise.resolve(subTask).then((sub) => { const sub2 = emitterMixin(sub) if (!sub.resend) { return sub2 } @@ -282,22 +269,34 @@ export default class StreamrClient extends EventEmitter { } async unsubscribe(opts) { - const sub = await this.messageStream.unsubscribe(opts) + const sub = await this.subscriber.unsubscribe(opts) if (sub && opts.emit) { opts.emit('unsubscribed') } } - getSubscriptions(...args) { - return this.messageStream.getAll(...args) - } + async resend(opts, fn) { + const task = this.subscriber.resend(opts) + if (!fn) { + return task + } - async ensureConnected() { - return this.connect() - } + // legacy event wrapper + const r = task.then((sub) => emitterMixin(sub)) - async ensureDisconnected() { - return this.disconnect() + Promise.resolve(r).then(async (sub) => { + sub.emit('resending') + for await (const msg of sub) { + await fn(msg.getParsedContent(), msg) + } + console.log('done') + sub.emit('resent') + return sub + }).catch((err) => { + this.emit('error', err) + }) + + return r } static generateEthereumAccount() { diff --git a/src/SubscribedStreamPartition.js b/src/SubscribedStreamPartition.js deleted file mode 100644 index 81582aa7f..000000000 --- a/src/SubscribedStreamPartition.js +++ /dev/null @@ -1,113 +0,0 @@ -import { Utils, MessageLayer } from 'streamr-client-protocol' -import memoize from 'promise-memoize' - -import SignatureRequiredError from './errors/SignatureRequiredError' - -const { StreamMessageValidator } = Utils -const { StreamMessage } = MessageLayer - -const memoizeOpts = { - maxAge: 15 * 60 * 1000, - maxErrorAge: 60 * 1000, -} - -export default class SubscribedStreamPartition { - constructor(client, streamId, streamPartition) { - this._client = client - this.streamId = streamId - this.streamPartition = streamPartition - this.subscriptions = {} - this.getStream = memoize(this.getStream.bind(this), memoizeOpts) - this.validator = new StreamMessageValidator({ - getStream: this.getStream, - isPublisher: memoize(async (publisherId, _streamId) => ( - this._client.isStreamPublisher(_streamId, publisherId) - ), memoizeOpts), - isSubscriber: memoize(async (ethAddress, _streamId) => ( - this._client.isStreamSubscriber(_streamId, ethAddress) - ), memoizeOpts), - }) - this.getPublishers = memoize(this.getPublishers.bind(this), memoizeOpts) - this.getSubscribers = memoize(this.getSubscribers.bind(this), memoizeOpts) - this.isValidPublisher = memoize(this.isValidPublisher.bind(this), memoizeOpts) - this.isValidSubscriber = memoize(this.isValidSubscriber.bind(this), memoizeOpts) - } - - async getPublishers() { - const publishers = await this._client.getStreamPublishers(this.streamId) - return publishers.reduce((obj, key) => ( - Object.assign(obj, { - [key]: true - }) - ), {}) - } - - async getSubscribers() { - const subscribers = await this._client.getStreamSubscribers(this.streamId) - return subscribers.reduce((obj, key) => ( - Object.assign(obj, { - [key]: true - }) - ), {}) - } - - async isValidPublisher(publisherId) { - return this._client.isStreamPublisher(this.streamId, publisherId) - } - - async isValidSubscriber(ethAddress) { - return this._client.isStreamSubscriber(this.streamId, ethAddress) - } - - async verifyStreamMessage(msg) { - // Check special cases controlled by the verifySignatures policy - const { options } = this._client - if (options.verifySignatures === 'never' && msg.messageType === StreamMessage.MESSAGE_TYPES.MESSAGE) { - return // no validation required - } - - if (options.verifySignatures === 'always' && !msg.signature) { - throw new SignatureRequiredError(msg) - } - - // In all other cases validate using the validator - await this.validator.validate(msg) // will throw with appropriate validation failure - } - - async getStream() { - return this._client.getStream(this.streamId) - } - - getSubscription(subscriptionId) { - return this.subscriptions[subscriptionId] - } - - getSubscriptions() { - return Object.values(this.subscriptions) || [] - } - - isSubscribing() { - return this.subscribing - } - - setSubscribing(value) { - this.subscribing = value - } - - emptySubscriptionsSet() { - return Object.keys(this.subscriptions).length === 0 - } - - addSubscription(sub) { - this.subscriptions[sub.id] = sub - } - - removeSubscription(sub) { - if (this.subscriptions[sub.id]) { - this.subscriptions[sub.id].stop() - delete this.subscriptions[sub.id] - } - } -} - -SubscribedStreamPartition.memoizeOpts = memoizeOpts diff --git a/src/Subscriber.js b/src/Subscriber.js deleted file mode 100644 index 27473ef00..000000000 --- a/src/Subscriber.js +++ /dev/null @@ -1,433 +0,0 @@ -import once from 'once' -import { ControlLayer, Errors } from 'streamr-client-protocol' - -import SubscribedStreamPartition from './SubscribedStreamPartition' -import RealTimeSubscription from './RealTimeSubscription' -import CombinedSubscription from './CombinedSubscription' -import Subscription from './Subscription' - -const { SubscribeRequest, UnsubscribeRequest, ControlMessage } = ControlLayer - -export default class Subscriber { - constructor(client) { - this.client = client - this.debug = client.debug.extend('Subscriber') - - this.subscribedStreamPartitions = {} - - this.onBroadcastMessage = this.onBroadcastMessage.bind(this) - this.client.connection.on(ControlMessage.TYPES.BroadcastMessage, this.onBroadcastMessage) - - this.onSubscribeResponse = this.onSubscribeResponse.bind(this) - this.client.connection.on(ControlMessage.TYPES.SubscribeResponse, this.onSubscribeResponse) - - this.onUnsubscribeResponse = this.onUnsubscribeResponse.bind(this) - this.client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, this.onUnsubscribeResponse) - - this.onClientConnected = this.onClientConnected.bind(this) - this.client.on('connected', this.onClientConnected) - - this.onClientDisconnected = this.onClientDisconnected.bind(this) - this.client.on('disconnected', this.onClientDisconnected) - - this.onErrorEmit = this.client.getErrorEmitter(this) - } - - onBroadcastMessage(msg) { - // Broadcast messages to all subs listening on stream-partition - const stream = this._getSubscribedStreamPartition(msg.streamMessage.getStreamId(), msg.streamMessage.getStreamPartition()) - if (!stream) { - this.debug('WARN: message received for stream with no subscriptions: %s', msg.streamMessage.getStreamId()) - return - } - - const verifyFn = once(() => stream.verifyStreamMessage(msg.streamMessage)) // ensure verification occurs only once - // sub.handleBroadcastMessage never rejects: on any error it emits an 'error' event on the Subscription - stream.getSubscriptions().forEach((sub) => sub.handleBroadcastMessage(msg.streamMessage, verifyFn)) - } - - onSubscribeResponse(response) { - if (!this.client.isConnected()) { return } - this.debug('onSubscribeResponse') - const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) - if (stream) { - stream.setSubscribing(false) - stream.getSubscriptions().filter((sub) => !sub.resending) - .forEach((sub) => sub.setState(Subscription.State.subscribed)) - } - this.debug('Client subscribed: streamId: %s, streamPartition: %s', response.streamId, response.streamPartition) - } - - onUnsubscribeResponse(response) { - this.debug('Client unsubscribed: streamId: %s, streamPartition: %s', response.streamId, response.streamPartition) - const stream = this._getSubscribedStreamPartition(response.streamId, response.streamPartition) - if (stream) { - stream.getSubscriptions().forEach((sub) => { - this._removeSubscription(sub) - sub.setState(Subscription.State.unsubscribed) - }) - } - - return this._checkAutoDisconnect() - } - - async onClientConnected() { - try { - if (!this.client.isConnected()) { return } - // Check pending subscriptions - Object.keys(this.subscribedStreamPartitions).forEach((key) => { - this.subscribedStreamPartitions[key].getSubscriptions().forEach((sub) => { - if (sub.getState() !== Subscription.State.subscribed) { - this._resendAndSubscribe(sub).catch(this.onErrorEmit) - } - }) - }) - } catch (err) { - this.onErrorEmit(err) - } - } - - onClientDisconnected() { - Object.keys(this.subscribedStreamPartitions).forEach((key) => { - const stream = this.subscribedStreamPartitions[key] - stream.setSubscribing(false) - stream.getSubscriptions().forEach((sub) => { - sub.onDisconnected() - }) - }) - } - - onErrorMessage(err) { - // not ideal, see error handler in client - if (!(err instanceof Errors.InvalidJsonError || err.reason instanceof Errors.InvalidJsonError)) { - return - } - // If there is an error parsing a json message in a stream, fire error events on the relevant subs - const stream = this._getSubscribedStreamPartition(err.streamMessage.getStreamId(), err.streamMessage.getStreamPartition()) - if (stream) { - stream.getSubscriptions().forEach((sub) => sub.handleError(err)) - } else { - this.debug('WARN: InvalidJsonError received for stream with no subscriptions: %s', err.streamId) - } - } - - subscribe(optionsOrStreamId, callback, legacyOptions) { - const options = this._validateParameters(optionsOrStreamId, callback) - - // Backwards compatibility for giving an options object as third argument - Object.assign(options, legacyOptions) - - this.debug('subscribe %o', options) - - if (!options.stream) { - throw new Error('subscribe: Invalid arguments: options.stream is not given') - } - - // Create the Subscription object and bind handlers - let sub - if (options.resend) { - sub = new CombinedSubscription({ - streamId: options.stream, - streamPartition: options.partition || 0, - callback, - options: options.resend, - propagationTimeout: this.client.options.gapFillTimeout, - resendTimeout: this.client.options.retryResendAfter, - orderMessages: this.client.options.orderMessages, - debug: this.debug, - }) - } else { - sub = new RealTimeSubscription({ - streamId: options.stream, - streamPartition: options.partition || 0, - callback, - options: options.resend, - propagationTimeout: this.client.options.gapFillTimeout, - resendTimeout: this.client.options.retryResendAfter, - orderMessages: this.client.options.orderMessages, - debug: this.debug, - }) - } - sub.on('gap', (from, to, publisherId, msgChainId) => { - this.debug('gap %o %o', { - id: sub.id, - streamId: sub.streamId, - streamPartition: sub.streamPartition, - }, { - from, - to, - publisherId, - msgChainId, - }) - if (!sub.resending) { - // eslint-disable-next-line no-underscore-dangle - this.client.resender._requestResend(sub, { - from, to, publisherId, msgChainId, - }).catch((err) => { - this.onErrorEmit(new Error(`Failed to send resend request: ${err.stack}`)) - }) - } - }) - sub.on('done', () => { - this.debug('done event for sub %s', sub.id) - this.unsubscribe(sub).catch(this.onErrorEmit) - }) - - // Add to lookups - this._addSubscription(sub) - - // If connected, emit a subscribe request - if (this.client.isConnected()) { - this._resendAndSubscribe(sub).catch(this.onErrorEmit) - } else if (this.client.options.autoConnect) { - this.client.ensureConnected() - } - - return sub - } - - async unsubscribe(sub) { - if (!sub || !sub.streamId) { - throw new Error('unsubscribe: please give a Subscription object as an argument!') - } - - const { streamId, streamPartition, id } = sub - const info = { - id, - streamId, - streamPartition, - } - - this.debug('unsubscribe %o', info) - - const sp = this._getSubscribedStreamPartition(streamId, streamPartition) - - // If this is the last subscription for this stream-partition, unsubscribe the client too - if (sp - && sp.getSubscriptions().length === 1 - && sub.getState() === Subscription.State.subscribed - ) { - this.debug('last subscription %o', info) - sub.setState(Subscription.State.unsubscribing) - return this._requestUnsubscribe(sub) - } - - if (sub.getState() !== Subscription.State.unsubscribing && sub.getState() !== Subscription.State.unsubscribed) { - this.debug('remove subcription %o', info) - this._removeSubscription(sub) - // Else the sub can be cleaned off immediately - sub.setState(Subscription.State.unsubscribed) - return this._checkAutoDisconnect() - } - return Promise.resolve() - } - - unsubscribeAll(streamId, streamPartition) { - if (!streamId) { - throw new Error('unsubscribeAll: a stream id is required!') - } else if (typeof streamId !== 'string') { - throw new Error('unsubscribe: stream id must be a string!') - } - - let streamPartitions = [] - - // Unsubscribe all subs for the given stream-partition - if (streamPartition) { - const sp = this._getSubscribedStreamPartition(streamId, streamPartition) - if (sp) { - streamPartitions = [sp] - } - } else { - streamPartitions = this._getSubscribedStreamPartitionsForStream(streamId) - } - - streamPartitions.forEach((sp) => { - sp.getSubscriptions().forEach((sub) => { - this.unsubscribe(sub) - }) - }) - } - - _getSubscribedStreamPartition(streamId, streamPartition) { - const key = streamId + streamPartition - return this.subscribedStreamPartitions[key] - } - - _getSubscribedStreamPartitionsForStream(streamId) { - // TODO: pretty crude method, could improve - return Object.values(this.subscribedStreamPartitions) - .filter((stream) => stream.streamId === streamId) - } - - _addSubscribedStreamPartition(subscribedStreamPartition) { - const key = subscribedStreamPartition.streamId + subscribedStreamPartition.streamPartition - this.subscribedStreamPartitions[key] = subscribedStreamPartition - } - - _deleteSubscribedStreamPartition(subscribedStreamPartition) { - const key = subscribedStreamPartition.streamId + subscribedStreamPartition.streamPartition - delete this.subscribedStreamPartitions[key] - } - - _addSubscription(sub) { - let sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) - if (!sp) { - sp = new SubscribedStreamPartition(this.client, sub.streamId, sub.streamPartition) - this._addSubscribedStreamPartition(sp) - } - sp.addSubscription(sub) - } - - _removeSubscription(sub) { - const sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) - if (!sp) { - return - } - sp.removeSubscription(sub) - if (sp.getSubscriptions().length === 0) { - this._deleteSubscribedStreamPartition(sp) - } - } - - getSubscriptions(streamId, streamPartition) { - let subs = [] - - if (streamPartition) { - const sp = this._getSubscribedStreamPartition(streamId, streamPartition) - if (sp) { - subs = sp.getSubscriptions() - } - } else { - const sps = this._getSubscribedStreamPartitionsForStream(streamId) - sps.forEach((sp) => sp.getSubscriptions().forEach((sub) => subs.push(sub))) - } - - return subs - } - - stop() { - this.subscribedStreamPartitions = {} - } - - async _requestSubscribe(sub) { - const sp = this._getSubscribedStreamPartition(sub.streamId, sub.streamPartition) - // never reuse subscriptions when incoming subscription needs resends - // i.e. only reuse realtime subscriptions - if (!sub.hasResendOptions()) { - const subscribedSubs = sp.getSubscriptions().filter((it) => ( - it.getState() === Subscription.State.subscribed - // don't resuse subscriptions currently resending - && !it.isResending() - )) - - if (subscribedSubs.length) { - // If there already is a subscribed subscription for this stream, this new one will just join it immediately - this.debug('_requestSubscribe: another subscription for same stream: %s, insta-subscribing', sub.streamId) - - setTimeout(() => { - sub.setState(Subscription.State.subscribed) - }) - return - } - } - - const sessionToken = await this.client.session.getSessionToken() - - if (sp.isSubscribing()) { - return - } - - // If this is the first subscription for this stream-partition, send a subscription request to the server - const request = new SubscribeRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - sessionToken, - requestId: this.client.resender.resendUtil.generateRequestId(), - }) - sp.setSubscribing(true) - this.debug('_requestSubscribe: subscribing client: %o', request) - await this.client.send(request).catch((err) => { - sub.setState(Subscription.State.unsubscribed) - const error = new Error(`Failed to sendnsubscribe request: ${err.stack}`) - this.onErrorEmit(error) - throw error - }) - } - - async _requestUnsubscribe(sub) { - this.debug('Client unsubscribing stream %o partition %o', sub.streamId, sub.streamPartition) - const unsubscribeRequest = new UnsubscribeRequest({ - streamId: sub.streamId, - streamPartition: sub.streamPartition, - requestId: this.client.resender.resendUtil.generateRequestId(), - }) - await this.client.connection.send(unsubscribeRequest).catch((err) => { - sub.setState(Subscription.State.subscribed) - const error = new Error(`Failed to send unsubscribe request: ${err.stack}`) - this.onErrorEmit(error) - throw error - }) - return this._checkAutoDisconnect() - } - - async _checkAutoDisconnect() { - // Disconnect if no longer subscribed to any streams - if (this.client.options.autoDisconnect && Object.keys(this.subscribedStreamPartitions).length === 0) { - this.debug('Disconnecting due to no longer being subscribed to any streams') - return this.client.disconnect() - } - return Promise.resolve() - } - - // eslint-disable-next-line class-methods-use-this - _validateParameters(optionsOrStreamId, callback) { - if (!optionsOrStreamId) { - throw new Error('subscribe/resend: Invalid arguments: options is required!') - } else if (!callback) { - throw new Error('subscribe/resend: Invalid arguments: callback is required!') - } - - // Backwards compatibility for giving a streamId as first argument - let options - if (typeof optionsOrStreamId === 'string') { - options = { - stream: optionsOrStreamId, - } - } else if (typeof optionsOrStreamId === 'object') { - // shallow copy - options = { - ...optionsOrStreamId - } - } else { - throw new Error(`subscribe/resend: options must be an object! Given: ${optionsOrStreamId}`) - } - - return options - } - - async _resendAndSubscribe(sub) { - if (sub.getState() === Subscription.State.subscribing || sub.resending) { - return Promise.resolve() - } - - sub.setState(Subscription.State.subscribing) - return Promise.all([ - this._requestSubscribe(sub), - // eslint-disable-next-line no-underscore-dangle - sub.hasResendOptions() && this.client.resender._requestResend(sub), - ]) - - // const onSubscribed = new Promise((resolve, reject) => { - // this.debug('add resend on sub') - /// / Once subscribed, ask for a resend - // sub.once('subscribed', () => { - /// / eslint-disable-next-line no-underscore-dangle - // resolve(this.client.resender._requestResend(sub)) - // }) - // sub.once('error', reject) - // }) - // await this._requestSubscribe(sub) - - // return onSubscribed - } -} diff --git a/src/Subscription.js b/src/Subscription.js deleted file mode 100644 index 126b67029..000000000 --- a/src/Subscription.js +++ /dev/null @@ -1,110 +0,0 @@ -import EventEmitter from 'eventemitter3' -import debugFactory from 'debug' -import uniqueId from 'lodash.uniqueid' - -const DEFAULT_PROPAGATION_TIMEOUT = 5000 -const DEFAULT_RESEND_TIMEOUT = 5000 - -/* -'interface' containing the default parameters and functionalities common to every subscription (Combined, RealTime and Historical) - */ -export default class Subscription extends EventEmitter { - constructor({ - streamId, - streamPartition, - callback, - propagationTimeout = DEFAULT_PROPAGATION_TIMEOUT, - resendTimeout = DEFAULT_RESEND_TIMEOUT, - debug - }) { - super() - - if (!callback) { - throw new Error('No callback given!') - } - this.streamId = streamId - this.streamPartition = streamPartition - this.callback = callback - const id = uniqueId('sub') - this.id = id - if (debug) { - this.debug = debug.extend(this.constructor.name).extend(id) - } else { - this.debug = debugFactory(`StreamrClient::${this.constructor.name}`).extend(id) - } - - if (!streamId) { - throw new Error('No stream id given!') - } - this.propagationTimeout = propagationTimeout - this.resendTimeout = resendTimeout - this.state = Subscription.State.unsubscribed - } - - async waitForSubscribed() { - if (this._subscribedPromise) { - return this._subscribedPromise - } - - const subscribedPromise = new Promise((resolve, reject) => { - if (this.state === Subscription.State.subscribed) { - resolve() - return - } - let onError - const onSubscribed = () => { - this.off('error', onError) - resolve() - } - onError = (err) => { - this.off('subscribed', onSubscribed) - reject(err) - } - - const onUnsubscribed = () => { - if (this._subscribedPromise === subscribedPromise) { - this._subscribedPromise = undefined - } - } - - this.once('subscribed', onSubscribed) - this.once('unsubscribed', onUnsubscribed) - this.once('error', reject) - }).then(() => this).finally(() => { - if (this._subscribedPromise === subscribedPromise) { - this._subscribedPromise = undefined - } - }) - - this._subscribedPromise = subscribedPromise - return this._subscribedPromise - } - - emit(event, ...args) { - this.debug('emit', event) - return super.emit(event, ...args) - } - - getState() { - return this.state - } - - setState(state) { - this.debug(`Subscription: Stream ${this.streamId} state changed ${this.state} => ${state}`) - this.state = state - this.emit(state) - } - - /* eslint-disable class-methods-use-this */ - onDisconnected() { - throw new Error('Must be defined in child class') - } - /* eslint-enable class-methods-use-this */ -} - -Subscription.State = { - unsubscribed: 'unsubscribed', - subscribing: 'subscribing', - subscribed: 'subscribed', - unsubscribing: 'unsubscribing', -} diff --git a/src/errors/AuthFetchError.js b/src/errors/AuthFetchError.js deleted file mode 100644 index 63fba0969..000000000 --- a/src/errors/AuthFetchError.js +++ /dev/null @@ -1,13 +0,0 @@ -export default class AuthFetchError extends Error { - constructor(message, response, body) { - // add leading space if there is a body set - const bodyMessage = body ? ` ${(typeof body === 'string' ? body : JSON.stringify(body))}` : '' - super(message + bodyMessage) - this.response = response - this.body = body - - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor) - } - } -} diff --git a/src/errors/FailedToPublishError.js b/src/errors/FailedToPublishError.js deleted file mode 100644 index 8dd0e0d58..000000000 --- a/src/errors/FailedToPublishError.js +++ /dev/null @@ -1,11 +0,0 @@ -export default class FailedToPublishError extends Error { - constructor(streamId, msg, reason) { - super(`Failed to publish to stream ${streamId} due to: ${reason}. Message was: ${msg}`) - this.streamId = streamId - this.msg = msg - this.reason = reason - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor) - } - } -} diff --git a/src/errors/InvalidGroupKeyError.js b/src/errors/InvalidGroupKeyError.js deleted file mode 100644 index b27ae946f..000000000 --- a/src/errors/InvalidGroupKeyError.js +++ /dev/null @@ -1,8 +0,0 @@ -export default class InvalidGroupKeyError extends Error { - constructor(message) { - super(message) - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor) - } - } -} diff --git a/src/errors/InvalidGroupKeyRequestError.js b/src/errors/InvalidGroupKeyRequestError.js deleted file mode 100644 index 76fc00a30..000000000 --- a/src/errors/InvalidGroupKeyRequestError.js +++ /dev/null @@ -1,8 +0,0 @@ -export default class InvalidGroupKeyRequestError extends Error { - constructor(message) { - super(message) - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor) - } - } -} diff --git a/src/errors/InvalidGroupKeyResponseError.js b/src/errors/InvalidGroupKeyResponseError.js deleted file mode 100644 index 42754b85a..000000000 --- a/src/errors/InvalidGroupKeyResponseError.js +++ /dev/null @@ -1,8 +0,0 @@ -export default class InvalidGroupKeyResponseError extends Error { - constructor(message) { - super(message) - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor) - } - } -} diff --git a/src/errors/InvalidMessageTypeError.js b/src/errors/InvalidMessageTypeError.js deleted file mode 100644 index 8e3e1313f..000000000 --- a/src/errors/InvalidMessageTypeError.js +++ /dev/null @@ -1,8 +0,0 @@ -export default class InvalidMessageTypeError extends Error { - constructor(message) { - super(message) - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor) - } - } -} diff --git a/src/errors/SignatureRequiredError.js b/src/errors/SignatureRequiredError.js deleted file mode 100644 index 294650701..000000000 --- a/src/errors/SignatureRequiredError.js +++ /dev/null @@ -1,15 +0,0 @@ -import { Errors } from 'streamr-client-protocol' - -const EMPTY_MESSAGE = { - serialize() {} -} - -export default class SignatureRequiredError extends Errors.ValidationError { - constructor(streamMessage = EMPTY_MESSAGE) { - super(`Client requires data to be signed. Message: ${streamMessage.serialize()}`) - this.streamMessage = streamMessage - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor) - } - } -} diff --git a/src/errors/UnableToDecryptError.js b/src/errors/UnableToDecryptError.js deleted file mode 100644 index 0e097e09f..000000000 --- a/src/errors/UnableToDecryptError.js +++ /dev/null @@ -1,6 +0,0 @@ -export default class UnableToDecryptError extends Error { - constructor(streamMessage) { - super(`Unable to decrypt ${streamMessage.getSerializedContent()}`) - this.streamMessage = streamMessage - } -} diff --git a/src/errors/VerificationFailedError.js b/src/errors/VerificationFailedError.js deleted file mode 100644 index d6ba7731a..000000000 --- a/src/errors/VerificationFailedError.js +++ /dev/null @@ -1,10 +0,0 @@ -export default class VerificationFailedError extends Error { - constructor(streamMessage, cause) { - super(`Verification failed for message: ${streamMessage.serialize()}, cause: ${cause.stack || cause}`) - this.streamMessage = streamMessage - this.cause = cause - if (Error.captureStackTrace) { - Error.captureStackTrace(this, this.constructor) - } - } -} diff --git a/src/Signer.js b/src/publish/Signer.js similarity index 100% rename from src/Signer.js rename to src/publish/Signer.js diff --git a/src/Publisher.js b/src/publish/index.js similarity index 96% rename from src/Publisher.js rename to src/publish/index.js index 963f5dbd5..63bdca9f0 100644 --- a/src/Publisher.js +++ b/src/publish/index.js @@ -5,10 +5,22 @@ import randomstring from 'randomstring' import LRU from 'quick-lru' import { ethers } from 'ethers' +import Stream from '../rest/domain/Stream' +import { uuid, CacheAsyncFn, CacheFn, LimitAsyncFnByKey } from '../utils' + import Signer from './Signer' -import Stream from './rest/domain/Stream' -import FailedToPublishError from './errors/FailedToPublishError' -import { uuid, CacheAsyncFn, CacheFn, LimitAsyncFnByKey } from './utils' + +export class FailedToPublishError extends Error { + constructor(streamId, msg, reason) { + super(`Failed to publish to stream ${streamId} due to: ${reason}. Message was: ${msg}`) + this.streamId = streamId + this.msg = msg + this.reason = reason + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor) + } + } +} const { StreamMessage, MessageID, MessageRef } = MessageLayer diff --git a/src/rest/LoginEndpoints.js b/src/rest/LoginEndpoints.js index 88e36c40d..fb5110db4 100644 --- a/src/rest/LoginEndpoints.js +++ b/src/rest/LoginEndpoints.js @@ -1,7 +1,6 @@ import { getEndpointUrl } from '../utils' -import AuthFetchError from '../errors/AuthFetchError' -import authFetch from './authFetch' +import authFetch, { AuthFetchError } from './authFetch' async function getSessionToken(url, props) { return authFetch( diff --git a/src/rest/authFetch.js b/src/rest/authFetch.js index 2e7656007..7fbc5b110 100644 --- a/src/rest/authFetch.js +++ b/src/rest/authFetch.js @@ -1,13 +1,26 @@ import fetch from 'node-fetch' import Debug from 'debug' -import AuthFetchError from '../errors/AuthFetchError' import { getVersionString } from '../utils' export const DEFAULT_HEADERS = { 'Streamr-Client': `streamr-client-javascript/${getVersionString()}`, } +export class AuthFetchError extends Error { + constructor(message, response, body) { + // add leading space if there is a body set + const bodyMessage = body ? ` ${(typeof body === 'string' ? body : JSON.stringify(body).slice(0, 1024))}...` : '' + super(message + bodyMessage) + this.response = response + this.body = body + + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor) + } + } +} + const debug = Debug('StreamrClient:utils:authfetch') let ID = 0 diff --git a/src/subscribe/index.js b/src/subscribe/index.js index 0e7adb8ab..4a9e616a5 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -5,8 +5,7 @@ import pMemoize from 'p-memoize' import pLimit from 'p-limit' import { ControlLayer, MessageLayer, Utils, Errors } from 'streamr-client-protocol' -import SignatureRequiredError from '../errors/SignatureRequiredError' -import { uuid, CacheAsyncFn, pOrderedResolve, Defer } from '../utils' +import { uuid, CacheAsyncFn, pOrderedResolve } from '../utils' import { endStream, pipeline, CancelableGenerator } from '../utils/iterators' const { OrderingUtil, StreamMessageValidator } = Utils @@ -20,6 +19,20 @@ const { const { MessageRef, StreamMessage } = MessageLayer +const EMPTY_MESSAGE = { + serialize() {} +} + +export class SignatureRequiredError extends Errors.ValidationError { + constructor(streamMessage = EMPTY_MESSAGE) { + super(`Client requires data to be signed. Message: ${streamMessage.serialize()}`) + this.streamMessage = streamMessage + if (Error.captureStackTrace) { + Error.captureStackTrace(this, this.constructor) + } + } +} + /** * Convert allSettled results into a thrown Aggregate error if necessary. */ @@ -322,16 +335,21 @@ function MessagePipeline(client, opts = {}, onFinally = () => {}) { }, orderingUtil, ], async (err) => { + console.log('FINALLY endStream >>', err) try { await endStream(stream, err) + console.log('FINALLY endStream <<') } finally { + console.log('FINALLY onFinally >>') await onFinally(err) + console.log('FINALLY onFinally <<') } }) return Object.assign(p, { stream, done: () => { + console.log('done?', stream.writable) if (stream.writable) { stream.end() } @@ -472,6 +490,7 @@ async function getResendStream(client, opts) { ControlMessage.TYPES.ResendResponseNoResend, ], }).then((v) => { + console.log('done') msgs.done() return v }, (err) => { diff --git a/src/utils/iterators.js b/src/utils/iterators.js index 6ef514c3f..be18feb86 100644 --- a/src/utils/iterators.js +++ b/src/utils/iterators.js @@ -1,23 +1,71 @@ -import { finished, Readable, pipeline as streamPipeline } from 'stream' +import { finished, Readable, pipeline as streamPipeline } from 'readable-stream' import { promisify } from 'util' import pMemoize from 'p-memoize' -import { Defer, pTimeout } from '../utils' +import { Defer, pTimeout } from './index' -const pFinished = promisify(finished) +Readable.from = function from(iterable, opts) { + let iterator + if (iterable && iterable[Symbol.asyncIterator]) { + iterator = iterable[Symbol.asyncIterator]() + } else if (iterable && iterable[Symbol.iterator]) { + iterator = iterable[Symbol.iterator]() + } else { + throw new Error('invalid arg type') + } + + const readable = new Readable({ + objectMode: true, + ...opts + }) + + // Reading boolean to protect against _read + // being called before last iteration completion. + let reading = false + + async function next() { + try { + const { value, done } = await iterator.next() + if (done) { + readable.push(null) + } else if (readable.push(await value)) { + next() + } else { + reading = false + } + } catch (err) { + readable.destroy(err) + } + } + + // eslint-disable-next-line no-underscore-dangle + readable._read = function _read() { + if (!reading) { + reading = true + next() + } + } + + return readable +} export async function endStream(stream, optionalErr) { - // ends stream + waits for end - stream.destroy(optionalErr) - await true - try { - await pFinished(stream) - } catch (err) { - if (err.code === 'ERR_STREAM_PREMATURE_CLOSE') { - await pFinished(stream, optionalErr) + if (!stream.readable && !stream.writable) { + return promisify(stream.destroy.bind(stream))(optionalErr) + } + + if (stream.writable) { + if (optionalErr) { + await promisify(stream.destroy.bind(stream))(optionalErr) + } else { + await promisify(stream.end.bind(stream))(optionalErr) } } + + if (stream.readable) { + await promisify(stream.destroy.bind(stream))(optionalErr) + } } /** @@ -338,12 +386,24 @@ export function pipeline(iterables = [], onFinally, opts) { stream = nextIterable } - if (prev && getIsStream(nextIterable)) { + const nextStream = getIsStream(nextIterable) ? nextIterable : undefined + if (prev && nextStream) { const input = getIsStream(prev) ? prev : Readable.from(prev) - nextIterable = streamPipeline(input, nextIterable, () => { - // ignore error + nextIterable = iteratorFinally((async function* pipeStream() { + input.pipe(nextStream) + yield* nextStream + }()), async (err) => { + try { + await endStream(nextStream, err) + } finally { + await endStream(input) + } }) } + if (nextStream) { + nextStream.once('error', (er) => console.error({ er })) + } + try { yield* nextIterable } catch (err) { @@ -351,6 +411,10 @@ export function pipeline(iterables = [], onFinally, opts) { error = err } throw err + } finally { + if (nextStream) { + await endStream(nextStream) + } } }()), async (err) => { if (!cancelled && err) { diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index e71eb6993..a154e66aa 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -198,7 +198,7 @@ describe('StreamrClient', () => { }) }) - describe('resend', () => { + describe.only('resend', () => { let stream let timestamps = [] diff --git a/test/integration/Stream.test.js b/test/integration/Subscriber.test.js similarity index 100% rename from test/integration/Stream.test.js rename to test/integration/Subscriber.test.js diff --git a/test/integration/StreamResends.test.js b/test/integration/SubscriberResends.test.js similarity index 77% rename from test/integration/StreamResends.test.js rename to test/integration/SubscriberResends.test.js index 905403999..d2fbddfb4 100644 --- a/test/integration/StreamResends.test.js +++ b/test/integration/SubscriberResends.test.js @@ -1,10 +1,9 @@ import { ControlLayer } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' -import { uid, fakePrivateKey, describeRepeats, collect, Msg } from '../utils' +import { Msg, uid, collect, describeRepeats, fakePrivateKey, getWaitForStorage, getPublishTestMessages } from '../utils' import StreamrClient from '../../src' import Connection from '../../src/Connection' -import MessageStream from '../../src/subscribe' import config from './config' @@ -13,6 +12,7 @@ const { ControlMessage } = ControlLayer /* eslint-disable no-await-in-loop */ const WAIT_FOR_STORAGE_TIMEOUT = 6000 +const MAX_MESSAGES = 5 describeRepeats('resends', () => { let expectErrors = 0 // check no errors by default @@ -20,7 +20,9 @@ describeRepeats('resends', () => { let client let stream let published - let M + let publishTestMessages + let waitForStorage + let subscriber const createClient = (opts = {}) => { const c = new StreamrClient({ @@ -38,69 +40,26 @@ describeRepeats('resends', () => { return c } - async function waitForStorage({ streamId, streamPartition = 0, msg, timeout = 5000 }) { - const start = Date.now() - let last - // eslint-disable-next-line no-constant-condition - while (true) { - const duration = Date.now() - start - if (duration > timeout) { - client.debug('waitForStorage timeout %o', { - timeout, - duration - }, { - msg, - last: last.map((l) => l.content), - }) - const err = new Error(`timed out after ${duration}ms waiting for message`) - err.msg = msg - throw err - } - - last = await client.getStreamLast({ - streamId, - streamPartition, - count: 3, - }) - - let found = false - for (const { content } of last) { - if (content.value === msg.value) { - found = true - break - } - } - - if (found) { - return - } - - client.debug('message not found, retrying... %o', { - msg, last: last.map(({ content }) => content) - }) - await wait(500) - } - } - beforeAll(async () => { - // eslint-disable-next-line require-atomic-updates client = createClient() - await client.session.getSessionToken() + subscriber = client.subscriber - M = new MessageStream(client) + // eslint-disable-next-line require-atomic-updates + client.debug('connecting before test >>') + await Promise.all([ + client.connect(), + client.session.getSessionToken(), + ]) stream = await client.createStream({ - name: uid('stream'), + name: uid('stream') }) + client.debug('connecting before test <<') - published = [] - await client.connect() - for (let i = 0; i < 5; i++) { - const message = Msg() - // eslint-disable-next-line no-await-in-loop - await client.publish(stream.id, message) - published.push(message) - await wait(50) - } + publishTestMessages = getPublishTestMessages(client, stream.id) + + published = await publishTestMessages(MAX_MESSAGES) + + waitForStorage = getWaitForStorage(client) }) beforeAll(async () => { @@ -149,26 +108,26 @@ describeRepeats('resends', () => { }) await wait(3000) - const sub = await M.resend({ + const sub = await subscriber.resend({ streamId: emptyStream.id, last: 5, }) const receivedMsgs = await collect(sub) expect(receivedMsgs).toHaveLength(0) - expect(M.count(emptyStream.id)).toBe(0) + expect(subscriber.count(emptyStream.id)).toBe(0) }) it('resendSubscribe with nothing to resend', async () => { emptyStream = await client.createStream({ name: uid('stream') }) - const sub = await M.resendSubscribe({ + const sub = await subscriber.resendSubscribe({ streamId: emptyStream.id, last: 5, }) - expect(M.count(emptyStream.id)).toBe(1) + expect(subscriber.count(emptyStream.id)).toBe(1) const message = Msg() // eslint-disable-next-line no-await-in-loop await client.publish(emptyStream.id, message) @@ -180,7 +139,7 @@ describeRepeats('resends', () => { break } expect(received).toHaveLength(1) - expect(M.count(emptyStream.id)).toBe(0) + expect(subscriber.count(emptyStream.id)).toBe(0) }) }) @@ -196,18 +155,18 @@ describeRepeats('resends', () => { }, WAIT_FOR_STORAGE_TIMEOUT * 1.2) it('requests resend', async () => { - const sub = await M.resend({ + const sub = await subscriber.resend({ streamId: stream.id, last: published.length, }) const receivedMsgs = await collect(sub) expect(receivedMsgs).toHaveLength(published.length) expect(receivedMsgs).toEqual(published) - expect(M.count(stream.id)).toBe(0) + expect(subscriber.count(stream.id)).toBe(0) }) it('requests resend number', async () => { - const sub = await M.resend({ + const sub = await subscriber.resend({ streamId: stream.id, last: 2, }) @@ -215,11 +174,11 @@ describeRepeats('resends', () => { const receivedMsgs = await collect(sub) expect(receivedMsgs).toHaveLength(2) expect(receivedMsgs).toEqual(published.slice(-2)) - expect(M.count(stream.id)).toBe(0) + expect(subscriber.count(stream.id)).toBe(0) }) it('closes stream', async () => { - const sub = await M.resend({ + const sub = await subscriber.resend({ streamId: stream.id, last: published.length, }) @@ -229,14 +188,14 @@ describeRepeats('resends', () => { received.push(m) } expect(received).toHaveLength(published.length) - expect(M.count(stream.id)).toBe(0) + expect(subscriber.count(stream.id)).toBe(0) expect(sub.stream.readable).toBe(false) expect(sub.stream.writable).toBe(false) }) describe('resendSubscribe', () => { it('sees resends and realtime', async () => { - const sub = await M.resendSubscribe({ + const sub = await subscriber.resendSubscribe({ streamId: stream.id, last: published.length, }) @@ -255,7 +214,7 @@ describeRepeats('resends', () => { const msgs = receivedMsgs expect(msgs).toHaveLength(published.length) expect(msgs).toEqual(published) - expect(M.count(stream.id)).toBe(0) + expect(subscriber.count(stream.id)).toBe(0) expect(sub.realtime.stream.readable).toBe(false) expect(sub.realtime.stream.writable).toBe(false) expect(sub.resend.stream.readable).toBe(false) @@ -263,7 +222,7 @@ describeRepeats('resends', () => { }) it('sees resends and realtime again', async () => { - const sub = await M.resendSubscribe({ + const sub = await subscriber.resendSubscribe({ streamId: stream.id, last: published.length, }) @@ -282,7 +241,7 @@ describeRepeats('resends', () => { const msgs = receivedMsgs expect(msgs).toHaveLength(published.length) expect(msgs).toEqual(published) - expect(M.count(stream.id)).toBe(0) + expect(subscriber.count(stream.id)).toBe(0) expect(sub.realtime.stream.readable).toBe(false) expect(sub.realtime.stream.writable).toBe(false) expect(sub.resend.stream.readable).toBe(false) @@ -290,12 +249,12 @@ describeRepeats('resends', () => { }) it('can return before start', async () => { - const sub = await M.resendSubscribe({ + const sub = await subscriber.resendSubscribe({ streamId: stream.id, last: published.length, }) - expect(M.count(stream.id)).toBe(1) + expect(subscriber.count(stream.id)).toBe(1) const message = Msg() await sub.return() @@ -309,13 +268,13 @@ describeRepeats('resends', () => { } expect(received).toHaveLength(0) - expect(M.count(stream.id)).toBe(0) + expect(subscriber.count(stream.id)).toBe(0) expect(sub.realtime.stream.readable).toBe(false) expect(sub.resend.stream.writable).toBe(false) }) it('can end asynchronously', async () => { - const sub = await M.resendSubscribe({ + const sub = await subscriber.resendSubscribe({ streamId: stream.id, last: published.length, }) @@ -343,7 +302,7 @@ describeRepeats('resends', () => { const msgs = receivedMsgs expect(msgs).toHaveLength(published.length) expect(msgs).toEqual(published) - expect(M.count(stream.id)).toBe(0) + expect(subscriber.count(stream.id)).toBe(0) expect(sub.realtime.stream.readable).toBe(false) expect(sub.resend.stream.writable).toBe(false) }) @@ -353,7 +312,7 @@ describeRepeats('resends', () => { client.connection.on(ControlMessage.TYPES.UnsubscribeResponse, (m) => { unsubscribeEvents.push(m) }) - const sub = await M.resendSubscribe({ + const sub = await subscriber.resendSubscribe({ streamId: stream.id, last: published.length, }) @@ -373,7 +332,7 @@ describeRepeats('resends', () => { const msgs = receivedMsgs expect(msgs).toHaveLength(END_AFTER) expect(msgs).toEqual(published.slice(0, END_AFTER)) - expect(M.count(stream.id)).toBe(0) + expect(subscriber.count(stream.id)).toBe(0) expect(sub.realtime.stream.readable).toBe(false) expect(sub.resend.stream.writable).toBe(false) }) diff --git a/test/unit/CombinedSubscription.test.js b/test/legacy/CombinedSubscription.test.js similarity index 100% rename from test/unit/CombinedSubscription.test.js rename to test/legacy/CombinedSubscription.test.js diff --git a/test/unit/HistoricalSubscription.test.js b/test/legacy/HistoricalSubscription.test.js similarity index 99% rename from test/unit/HistoricalSubscription.test.js rename to test/legacy/HistoricalSubscription.test.js index 582dc880d..939d5c364 100644 --- a/test/unit/HistoricalSubscription.test.js +++ b/test/legacy/HistoricalSubscription.test.js @@ -1,3 +1,4 @@ +/ import sinon from 'sinon' import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' diff --git a/test/unit/MessageCreationUtil.test.js b/test/legacy/MessageCreationUtil.test.js similarity index 100% rename from test/unit/MessageCreationUtil.test.js rename to test/legacy/MessageCreationUtil.test.js diff --git a/test/unit/RealTimeSubscription.test.js b/test/legacy/RealTimeSubscription.test.js similarity index 100% rename from test/unit/RealTimeSubscription.test.js rename to test/legacy/RealTimeSubscription.test.js diff --git a/test/unit/StreamrClient.test.js b/test/legacy/StreamrClient.test.js similarity index 100% rename from test/unit/StreamrClient.test.js rename to test/legacy/StreamrClient.test.js diff --git a/test/unit/SubscribedStreamPartition.test.js b/test/legacy/SubscribedStreamPartition.test.js similarity index 100% rename from test/unit/SubscribedStreamPartition.test.js rename to test/legacy/SubscribedStreamPartition.test.js diff --git a/test/unit/Signer.test.js b/test/unit/Signer.test.js index 1eff3dbcb..0695223d6 100644 --- a/test/unit/Signer.test.js +++ b/test/unit/Signer.test.js @@ -1,6 +1,6 @@ import { MessageLayer } from 'streamr-client-protocol' -import Signer from '../../src/Signer' +import Signer from '../../src/publish/Signer' const { StreamMessage, MessageID, MessageRef } = MessageLayer /* diff --git a/test/unit/iterators.test.js b/test/unit/iterators.test.js index 41192e25b..679f39e3e 100644 --- a/test/unit/iterators.test.js +++ b/test/unit/iterators.test.js @@ -1,4 +1,5 @@ -import { Readable, PassThrough } from 'stream' +import Debug from 'debug' +import { Readable, PassThrough } from 'readable-stream' import { wait } from 'streamr-test-utils' @@ -6,8 +7,8 @@ import { iteratorFinally, CancelableGenerator, pipeline } from '../../src/utils/ import { Defer } from '../../src/utils' const expected = [1, 2, 3, 4, 5, 6, 7, 8] - const WAIT = 20 +console.log = Debug('Streamr:: CONSOLE ') async function* generate(items = expected) { await wait(WAIT * 0.1) @@ -1182,8 +1183,11 @@ describe('Iterator Utils', () => { await wait(WAIT) onFinallyInnerAfter() }) + const inputStream = Readable.from(generate()) + const onInputStreamClose = jest.fn() + inputStream.once('close', onInputStreamClose) const p = pipeline([ - Readable.from(generate()), + inputStream, async function* Step1(s) { yield* s }, @@ -1199,6 +1203,8 @@ describe('Iterator Utils', () => { expect(onFinallyInner).toHaveBeenCalledTimes(1) expect(onFinallyInnerAfter).toHaveBeenCalledTimes(1) expect(received).toEqual(expected) + expect(inputStream.readable).toBe(false) + expect(onInputStreamClose).toHaveBeenCalledTimes(1) }) it('works with nested pipelines', async () => { @@ -1207,6 +1213,7 @@ describe('Iterator Utils', () => { await wait(WAIT) onFinallyInnerAfter() }) + const receivedStep1 = [] const receivedStep2 = [] const onFirstStreamClose = jest.fn() @@ -1216,6 +1223,7 @@ describe('Iterator Utils', () => { const inputStream = new PassThrough({ objectMode: true, }) + inputStream.id = 'inputStream' inputStream.once('close', onInputStreamClose) const p1 = pipeline([ inputStream, @@ -1228,12 +1236,14 @@ describe('Iterator Utils', () => { ], onFinallyInner) const firstStream = Readable.from(generate()) + firstStream.id = 'firststream' firstStream.once('close', onFirstStreamClose) let intermediateStream const p = pipeline([ firstStream, (s) => { intermediateStream = Readable.from(s).pipe(inputStream) + intermediateStream.id = 'intermediateStream' intermediateStream.once('close', onIntermediateStreamClose) return p1 }, From 663bb4d741350414c2b7ad5787e8e7944da17180 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Sat, 31 Oct 2020 13:00:15 -0600 Subject: [PATCH 127/517] WIP compatiblity with browser, streams buggy. --- .babelrc | 16 ++++------------ src/shim/ws.js | 4 +++- src/subscribe/index.js | 6 ------ src/utils/iterators.js | 8 +++----- test/browser/browser.html | 2 +- test/unit/iterators.test.js | 4 +--- webpack.config.js | 6 ++---- 7 files changed, 14 insertions(+), 32 deletions(-) diff --git a/.babelrc b/.babelrc index a08d9c6c0..209e25dc1 100644 --- a/.babelrc +++ b/.babelrc @@ -6,17 +6,9 @@ "corejs": 3, "loose": false, "targets": { - "browsers": [ - "> 1.5%", - "Opera >= 58", - "Safari >= 12", - "Edge >= 75", - "Firefox ESR", - "not dead", - "not ie <= 11", - "not ie_mob <= 11" - ] - } + "esmodules": true + }, + "exclude": ["transform-regenerator", "@babel/plugin-transform-regenerator"] }] ], "plugins": [ @@ -24,7 +16,7 @@ ["@babel/plugin-transform-runtime", { "corejs": false, "helpers": true, - "regenerator": true + "regenerator": false }], "@babel/plugin-transform-modules-commonjs", ["@babel/plugin-proposal-class-properties", { diff --git a/src/shim/ws.js b/src/shim/ws.js index c088e1fdc..ba3be47a6 100644 --- a/src/shim/ws.js +++ b/src/shim/ws.js @@ -1,4 +1,6 @@ // NB: THIS FILE MUST BE IN ES5 // In browsers, the ws package is replaced with this to use native websockets -export default typeof WebSocket !== 'undefined' ? WebSocket : window.WebSocket +export default typeof WebSocket !== 'undefined' ? WebSocket : function WebsocketWrap(url) { + return new window.WebSocket(url) +} diff --git a/src/subscribe/index.js b/src/subscribe/index.js index 4a9e616a5..90463c322 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -335,21 +335,16 @@ function MessagePipeline(client, opts = {}, onFinally = () => {}) { }, orderingUtil, ], async (err) => { - console.log('FINALLY endStream >>', err) try { await endStream(stream, err) - console.log('FINALLY endStream <<') } finally { - console.log('FINALLY onFinally >>') await onFinally(err) - console.log('FINALLY onFinally <<') } }) return Object.assign(p, { stream, done: () => { - console.log('done?', stream.writable) if (stream.writable) { stream.end() } @@ -490,7 +485,6 @@ async function getResendStream(client, opts) { ControlMessage.TYPES.ResendResponseNoResend, ], }).then((v) => { - console.log('done') msgs.done() return v }, (err) => { diff --git a/src/utils/iterators.js b/src/utils/iterators.js index be18feb86..1ce06e835 100644 --- a/src/utils/iterators.js +++ b/src/utils/iterators.js @@ -1,4 +1,4 @@ -import { finished, Readable, pipeline as streamPipeline } from 'readable-stream' +import { Readable } from 'stream' import { promisify } from 'util' import pMemoize from 'p-memoize' @@ -52,7 +52,8 @@ Readable.from = function from(iterable, opts) { export async function endStream(stream, optionalErr) { if (!stream.readable && !stream.writable) { - return promisify(stream.destroy.bind(stream))(optionalErr) + await promisify(stream.destroy.bind(stream))(optionalErr) + return } if (stream.writable) { @@ -400,9 +401,6 @@ export function pipeline(iterables = [], onFinally, opts) { } }) } - if (nextStream) { - nextStream.once('error', (er) => console.error({ er })) - } try { yield* nextIterable diff --git a/test/browser/browser.html b/test/browser/browser.html index 351715669..b9cec7f39 100644 --- a/test/browser/browser.html +++ b/test/browser/browser.html @@ -2,7 +2,7 @@ Test StreamrClient in Chrome Browser - + + + + + +

Real-time telemetrics from trams running in Helsinki, Finland.

+

Provided by the local public transport authority (HSL) over MQTT protocol.

+
+
+
+
+ - - -

Real-time telemetrics from trams running in Helsinki, Finland. Provided by the local public transport authority (HSL) over MQTT protocol.

+ } + + start() + From c5fe5f8e2dae5b7e8bff667618e1bf73c0e9dc84 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 15 Jan 2021 12:27:13 -0500 Subject: [PATCH 350/517] Relax disconnect/reconnect test further. --- test/integration/StreamConnectionState.test.js | 4 ++-- test/integration/StreamrClient.test.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/integration/StreamConnectionState.test.js b/test/integration/StreamConnectionState.test.js index f5487a886..0b58f7a97 100644 --- a/test/integration/StreamConnectionState.test.js +++ b/test/integration/StreamConnectionState.test.js @@ -404,8 +404,8 @@ describeRepeats('Connection State', () => { // check disconnect/connect actually happened expect(onConnectionMessage.mock.calls.length).toBeGreaterThanOrEqual(published.length) - expect(onConnected).toHaveBeenCalledTimes(published.length) - expect(onDisconnected).toHaveBeenCalledTimes(published.length) + expect(onConnected.mock.calls.length).toBeGreaterThanOrEqual(published.length) + expect(onDisconnected.mock.calls.length).toBeGreaterThanOrEqual(published.length) } finally { await Promise.all([ otherClient.disconnect(), diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index 202c60cbd..5c184fc6a 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -953,8 +953,8 @@ describeRepeats('StreamrClient', () => { // check disconnect/connect actually happened expect(onConnectionMessage.mock.calls.length).toBeGreaterThanOrEqual(published.length) - expect(onConnected).toHaveBeenCalledTimes(published.length + 1) - expect(onDisconnected).toHaveBeenCalledTimes(published.length + 1) + expect(onConnected.mock.calls.length).toBeGreaterThanOrEqual(published.length) + expect(onDisconnected.mock.calls.length).toBeGreaterThanOrEqual(published.length) } finally { await Promise.all([ otherClient.disconnect(), From 8bb129665c127d52520103d6c3f292fb4551122a Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 15 Jan 2021 12:53:29 -0500 Subject: [PATCH 351/517] Do proper before/after cleanup for disconnect/reconnect test. Increase timeout. --- .../integration/StreamConnectionState.test.js | 38 ++++++++++++------- test/integration/StreamrClient.test.js | 38 ++++++++++++------- 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/test/integration/StreamConnectionState.test.js b/test/integration/StreamConnectionState.test.js index 0b58f7a97..5dbee605e 100644 --- a/test/integration/StreamConnectionState.test.js +++ b/test/integration/StreamConnectionState.test.js @@ -347,18 +347,33 @@ describeRepeats('Connection State', () => { expect(subscriber.count(stream.id)).toBe(0) }) - it('should resubscribe on unexpected disconnection', async () => { - const otherClient = createClient({ - auth: client.options.auth, - }) + describe('resubscribe on unexpected disconnection', () => { + let otherClient - try { - await Promise.all([ + beforeEach(async () => { + otherClient = createClient({ + auth: client.options.auth, + }) + const tasks = [ client.connect(), + client.session.getSessionToken(), otherClient.connect(), otherClient.session.getSessionToken(), - ]) + ] + await Promise.allSettled(tasks) + await Promise.all(tasks) // throw if there were an error + }) + afterEach(async () => { + const tasks = [ + otherClient.disconnect(), + client.disconnect(), + ] + await Promise.allSettled(tasks) + await Promise.all(tasks) // throw if there were an error + }) + + it('should work', async () => { const done = Defer() const msgs = [] @@ -406,12 +421,7 @@ describeRepeats('Connection State', () => { expect(onConnectionMessage.mock.calls.length).toBeGreaterThanOrEqual(published.length) expect(onConnected.mock.calls.length).toBeGreaterThanOrEqual(published.length) expect(onDisconnected.mock.calls.length).toBeGreaterThanOrEqual(published.length) - } finally { - await Promise.all([ - otherClient.disconnect(), - client.disconnect(), - ]) - } - }, 20000) + }, 30000) + }) }) }) diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index 5c184fc6a..ff5e632b5 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -896,18 +896,33 @@ describeRepeats('StreamrClient', () => { expect(onMessageMsgs).toEqual(published) }) - it('should resubscribe on unexpected disconnection', async () => { - const otherClient = createClient({ - auth: client.options.auth, - }) + describe('resubscribe on unexpected disconnection', () => { + let otherClient - try { - await Promise.all([ + beforeEach(async () => { + otherClient = createClient({ + auth: client.options.auth, + }) + const tasks = [ client.connect(), + client.session.getSessionToken(), otherClient.connect(), otherClient.session.getSessionToken(), - ]) + ] + await Promise.allSettled(tasks) + await Promise.all(tasks) // throw if there were an error + }) + + afterEach(async () => { + const tasks = [ + otherClient.disconnect(), + client.disconnect(), + ] + await Promise.allSettled(tasks) + await Promise.all(tasks) // throw if there were an error + }) + it('should work', async () => { const done = Defer() const msgs = [] @@ -955,13 +970,8 @@ describeRepeats('StreamrClient', () => { expect(onConnectionMessage.mock.calls.length).toBeGreaterThanOrEqual(published.length) expect(onConnected.mock.calls.length).toBeGreaterThanOrEqual(published.length) expect(onDisconnected.mock.calls.length).toBeGreaterThanOrEqual(published.length) - } finally { - await Promise.all([ - otherClient.disconnect(), - client.disconnect(), - ]) - } - }, 20000) + }, 30000) + }) it('publish and subscribe a sequence of messages', async () => { client.enableAutoConnect() From 8f71024264cd2e00c65ff8853faefc86a2330337 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 7 Jan 2021 08:57:37 -0500 Subject: [PATCH 352/517] Verify individual messages arrived in browser test. --- test/browser/browser.js | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/test/browser/browser.js b/test/browser/browser.js index fc89a0452..8630f1163 100644 --- a/test/browser/browser.js +++ b/test/browser/browser.js @@ -25,12 +25,34 @@ describe('StreamrClient', () => { .assert.containsText('#result', 'subscribed') .click('button[id=publish]') .pause(3000) + .verify.containsText('#result', '{"msg":0}') + .verify.containsText('#result', '{"msg":1}') + .verify.containsText('#result', '{"msg":2}') + .verify.containsText('#result', '{"msg":3}') + .verify.containsText('#result', '{"msg":4}') + .verify.containsText('#result', '{"msg":5}') + .verify.containsText('#result', '{"msg":6}') + .verify.containsText('#result', '{"msg":7}') + .verify.containsText('#result', '{"msg":8}') + .verify.containsText('#result', '{"msg":9}') .assert.containsText('#result', '[{"msg":0},{"msg":1},{"msg":2},{"msg":3},{"msg":4},{"msg":5},{"msg":6},{"msg":7},{"msg":8},{"msg":9}]') .pause(3000) .click('button[id=resend]') .pause(6000) - // eslint-disable-next-line max-len - .assert.containsText('#result', 'Resend: [{"msg":0},{"msg":1},{"msg":2},{"msg":3},{"msg":4},{"msg":5},{"msg":6},{"msg":7},{"msg":8},{"msg":9}]') + .verify.containsText('#result', '{"msg":0}') + .verify.containsText('#result', '{"msg":1}') + .verify.containsText('#result', '{"msg":2}') + .verify.containsText('#result', '{"msg":3}') + .verify.containsText('#result', '{"msg":4}') + .verify.containsText('#result', '{"msg":5}') + .verify.containsText('#result', '{"msg":6}') + .verify.containsText('#result', '{"msg":7}') + .verify.containsText('#result', '{"msg":8}') + .verify.containsText('#result', '{"msg":9}') + .assert.containsText( + '#result', + 'Resend: [{"msg":0},{"msg":1},{"msg":2},{"msg":3},{"msg":4},{"msg":5},{"msg":6},{"msg":7},{"msg":8},{"msg":9}]', + ) .click('button[id=disconnect]') .assert.containsText('#result', 'disconnected') }) From 8ff719da0e14e84a0afd78f3ddac464e283d23f6 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 5 Jan 2021 14:22:10 -0500 Subject: [PATCH 353/517] Merge branch 'DU-2' into encryption-pipeline * DU-2: (199 commits) fix test fix typo re-add streamrNodeAddress to StreamrClient config add product creation into tests fix await oops another merge hazard fix manual merge hazard fix Create DUv2 products README update [4.2.0-alpha.13] bump version fix test fix comment remove commented-out code better naming withdraw process refactor addRevenue -> refreshRevenue update contracts add package-lock bump chromedriver ensure constructor-given options bubble down to the functions when withdrawing ... --- .eslintrc.js | 2 + .github/workflows/nodejs.yml | 8 +- README.md | 102 +- contracts/DataUnion.json | 1764 - contracts/DataUnionFactoryMainnet.json | 6163 +++ contracts/DataUnionFactorySidechain.json | 8461 ++++ contracts/DataUnionMainnet.json | 14011 ++++++ contracts/DataUnionSidechain.json | 36293 ++++++++++++++++ examples/web/web-example-metamask.html | 41 +- jest.config.js | 2 +- package-lock.json | 1495 +- package.json | 10 + src/Config.js | 49 +- src/Session.js | 12 +- src/StreamrClient.js | 27 +- src/publish/Signer.js | 26 +- src/publish/index.js | 18 +- src/rest/DataUnionEndpoints.js | 1247 +- src/stream/Encryption.js | 14 +- src/stream/KeyExchange.js | 2 +- src/utils/index.js | 28 + test/browser/browser.js | 2 +- test/flakey/DataUnionEndpoints.test.js | 284 - .../DataUnionEndpoints.test.js | 218 + .../adminWithdrawMember.test.js | 138 + .../adminWithdrawSigned.test.js | 155 + .../DataUnionEndpoints/calculate.test.js | 39 + .../DataUnionEndpoints/withdraw.test.js | 140 + .../DataUnionEndpoints/withdrawTo.test.js | 142 + test/integration/StreamEndpoints.test.js | 12 +- test/integration/config.js | 23 +- test/legacy/SubscribedStreamPartition.test.js | 2 +- test/unit/Session.test.js | 12 +- test/unit/Signer.test.js | 14 +- 34 files changed, 67514 insertions(+), 3442 deletions(-) delete mode 100644 contracts/DataUnion.json create mode 100644 contracts/DataUnionFactoryMainnet.json create mode 100644 contracts/DataUnionFactorySidechain.json create mode 100644 contracts/DataUnionMainnet.json create mode 100644 contracts/DataUnionSidechain.json delete mode 100644 test/flakey/DataUnionEndpoints.test.js create mode 100644 test/integration/DataUnionEndpoints/DataUnionEndpoints.test.js create mode 100644 test/integration/DataUnionEndpoints/adminWithdrawMember.test.js create mode 100644 test/integration/DataUnionEndpoints/adminWithdrawSigned.test.js create mode 100644 test/integration/DataUnionEndpoints/calculate.test.js create mode 100644 test/integration/DataUnionEndpoints/withdraw.test.js create mode 100644 test/integration/DataUnionEndpoints/withdrawTo.test.js diff --git a/.eslintrc.js b/.eslintrc.js index 51235765a..fdfe91128 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -20,6 +20,8 @@ module.exports = { } ], 'prefer-destructuring': 'warn', + 'object-curly-newline': 'off', + 'no-continue': 'off', 'max-classes-per-file': 'off', // javascript is not java // TODO check all errors/warnings and create separate PR 'promise/always-return': 'warn', diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index aa95bdeca..457ea6873 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -92,7 +92,7 @@ jobs: run: | sudo service mysql stop sudo ifconfig docker0 10.200.10.1/24 - ${GITHUB_WORKSPACE}/streamr-dev/streamr-docker-dev/bin.sh start --wait + ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server --except bridge_collected - name: Run Test run: npm run $TEST_NAME -- --maxWorkers=1 @@ -119,7 +119,7 @@ jobs: run: | sudo service mysql stop sudo ifconfig docker0 10.200.10.1/24 - ${GITHUB_WORKSPACE}/streamr-dev/streamr-docker-dev/bin.sh start --wait + ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server --except bridge_collected - uses: nick-invision/retry@v2 name: Run Test with: @@ -151,7 +151,7 @@ jobs: run: | sudo service mysql stop sudo ifconfig docker0 10.200.10.1/24 - ${GITHUB_WORKSPACE}/streamr-dev/streamr-docker-dev/bin.sh start nginx broker-node-storage-1 broker-node-no-storage-1 broker-node-no-storage-2 engine-and-editor tracker-1 --wait + ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server --except bridge_collected - name: test-browser timeout-minutes: 2 run: npm run test-browser @@ -181,7 +181,7 @@ jobs: run: | sudo service mysql stop sudo ifconfig docker0 10.200.10.1/24 - ${GITHUB_WORKSPACE}/streamr-dev/streamr-docker-dev/bin.sh start nginx broker-node-storage-1 broker-node-no-storage-1 broker-node-no-storage-2 engine-and-editor tracker-1 --wait + ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server --except bridge_collected - name: npm ci run: npm ci - name: benchmarks diff --git a/README.md b/README.md index 1b588ce21..bc420a532 100644 --- a/README.md +++ b/README.md @@ -302,54 +302,94 @@ sub.on('initial_resend_done', () => { ## Data Unions -This library provides functions for working with Data Unions. All of the below methods return a Promise. +This library provides functions for working with Data Unions. + +TODO: check all this documentation before merging/publishing, probably some of it is out of date (DU1 era) + +Data union functions take a third parameter, `options`, which are either overrides to options given in the constructor, or optional arguments to the function. + +| Property | Default | Description | +| :------------ | :-------------------- | :------------------------------------------------------------ | +| wallet | given in auth | ethers.js Wallet object to use to sign and send withdraw transaction | +| provider | mainnet | ethers.js Provider to use if wallet wasn't provided | +| confirmations | `1` | Number of blocks to wait after the withdraw transaction is mined | +| gasPrice | ethers.js supplied | Probably uses the network estimate +| dataUnion | - | Address or contract object of the data union that is the target of the operation +| tokenAddress | 0x0Cf0Ee637
88A0849fE52
97F3407f701
E122cC023 | Token used by the DU +| minimumWithdrawTokenWei | 1000000 | Threshold value set in AMB configs, smallest token amount that can pass over the bridge +| sidechainTokenAddress | TODO | sidechain token address +| factoryMainnetAddress | TODO | Data Union factory that creates a new Data Union +| sidechainAmbAddress | TODO | Arbitrary Message-passing Bridge (AMB), see [Tokenbridge github page](https://github.com/poanetwork/tokenbridge) +| payForSignatureTransport | `true` | Someone must pay for transporting the withdraw tx to mainnet, either us or bridge operator #### Admin functions -| Name | Returns | Description | -| :------------------------------------------------------- | :----------- | :---------------------------------------------------------- | -| deployDataUnion() | Transaction | Deploy a new Data Union | -| createSecret(dataUnionContractAddress, secret[, name]) | | Create a secret for a Data Union | -| dataUnionIsReady(address) | | Wait until a new Data Union is initialized by its Operator | -| addMembers(dataUnionContractAddress, memberAddressList) | | Add members | -| kick(dataUnionContractAddress, memberAddressList) | | Kick members out from Data Union | +| Name | Returns | Description | +| :-------------------------------------------------------- | :-------------------- | :--------------------------------------------------------- | +| deployDataUnion(options) | Dataunion contract | Deploy a new Data Union | +| createSecret(dataUnionContractAddress, secret[, name]) | | Create a secret for a Data Union | +| addMembers(memberAddressList, options) | Transaction receipt | Add members | +| kick(memberAddressList, options) | Transaction receipt | Kick members out from Data Union | +| withdrawMember(memberAddress, options) ||| +| withdrawToSigned(memberAddress, recipientAddress, signature, options) ||| +| setAdminFee(newFeeFraction, options) | Transaction receipt | `newFeeFraction` is a `Number` between 0.0 and 1.0 (inclusive) | + +Here's an example how to deploy a data union contract and set the admin fee: ```javascript +const client = new StreamrClient({ + auth: { privateKey }, +}) const dataUnion = await client.deployDataUnion() -dataUnion.address // already available before deployment -await dataUnion.deployed() // waits until contract is deployed -await dataUnion.isReady() // waits until data union is operated +await client.setAdminFee(0.3, { dataUnion }) ``` #### Member functions -| Name | Returns | Description | -| :------------------------------------------------------------ | :----------- | :------------------------------------------------------------ | -| joinDataUnion(dataUnionContractAddress[, secret]) | JoinRequest | Join a Data Union | -| hasJoined(dataUnionContractAddress[, memberAddress]) | | Wait until member has been accepted | -| validateProof(dataUnionContractAddress, options) | true/false | Check that server is giving a proof that allows withdrawing | -| withdraw(dataUnionContractAddress, options) | Receipt | Withdraw funds from Data Union | -| withdrawFor(memberAddress, dataUnionContractAddress, options) | Receipt | Pay for withdraw transaction on behalf of a Data Union member | -| withdrawTo(recipientAddress, dataUnionContractAddress, options) | Receipt | Donate/move your earnings to recipientAddress instead of your memberAddress | +| Name | Returns | Description | +| :---------------------------------------------------------------- | :------------ | :------------------------------------------------------------ | +| joinDataUnion(options) | JoinRequest | Join a Data Union | +| hasJoined([memberAddress], options) | - | Wait until member has been accepted | +| withdraw(options) | Transaction receipt | Withdraw funds from Data Union | +| withdrawTo(recipientAddress, dataUnionContractAddress, options) | Transaction receipt | Donate/move your earnings to recipientAddress instead of your memberAddress | +| signWithdrawTo(recipientAddress, options) | Signature (string) | Signature that can be used to withdraw tokens to given recipientAddress | +| signWithdrawAmountTo(recipientAddress, amountTokenWei, options) | Signature (string) | Signature that can be used to withdraw tokens to given recipientAddress | -The options object for withdraw functions above may contain following overrides: +Here's an example how to sign off on a withdraw to (any) recipientAddress: -| Property | Default | Description | -| :------------- | :--------- | :------------------------------------------------------------ | -| wallet | auth | ethers.js Wallet object to use to sign and send withdraw transaction | -| provider | mainnet | ethers.js Provider to use if wallet wasn't provided | -| confirmations | 1 | Number of blocks to wait after the withdraw transaction is mined | -| gasPrice | ethers.js | Probably uses the network estimate | +```javascript +const client = new StreamrClient({ + auth: { privateKey }, + dataUnion, +}) +const signature = await client.signWithdrawTo(recipientAddress) +``` #### Query functions These are available for everyone and anyone, to query publicly available info from a Data Union: -| Name | Returns | Description | -| :--------------------------------------------------------- | :--------------------------------- | :--------------------------- | -| getMemberStats(dataUnionContractAddress[, memberAddress]) | {earnings, proof, ...} | Get member's stats | -| getDataUnionStats(dataUnionContractAddress) | {memberCount, totalEarnings, ...} | Get Data Union's statistics | -| getMembers(dataUnionContractAddress) | [{address, earnings}, ...] | Get Data Union's members | +| Name | Returns | Description | +| :-------------------------------------------------------- | :-------------------------------------- | :-------------------------- | +| getMemberStats(dataUnionContractAddress[, memberAddress]) | {earnings, proof, ...} | Get member's stats | +| getDataUnionStats(dataUnionContractAddress) | {activeMemberCount, totalEarnings, ...} | Get Data Union's statistics | +| ~~getMembers(dataUnionContractAddress)~~ | | NOT available in DU2 at the moment | +| getAdminFee(options) | `Number` between 0.0 and 1.0 (inclusive)| Admin's cut from revenues | +| getAdminAddress(options) | Ethereum address | Data union admin's address | +| getDataUnionStats(options) | Stats object | Various metrics from the smart contract | +| getMemberStats([memberAddress], options) | Member stats object | Various metrics from the smart contract | +| getMemberBalance([memberAddress], options) | `BigNumber` withdrawable DATA tokens in the DU | | +| getTokenBalance(address, options) | `BigNumber` | Mainnet DATA token balance | +| getDataUnionVersion(contractAddress) | `0`, `1` or `2` | `0` if the contract is not a data union | + +Here's an example how to get a member's withdrawable token balance (in "wei", where 1 DATA = 10^18 wei) + +```javascript +const client = new StreamrClient({ + dataUnion, +}) +const withdrawableWei = await client.getMemberBalance(memberAddress) +``` ## Utility functions diff --git a/contracts/DataUnion.json b/contracts/DataUnion.json deleted file mode 100644 index b48c64879..000000000 --- a/contracts/DataUnion.json +++ /dev/null @@ -1,1764 +0,0 @@ -{ - "contractName": "DataunionVault", - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "operator", - "type": "address" - }, - { - "internalType": "string", - "name": "joinPartStreamId", - "type": "string" - }, - { - "internalType": "address", - "name": "tokenAddress", - "type": "address" - }, - { - "internalType": "uint256", - "name": "blockFreezePeriodSeconds", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "adminFeeFraction", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "adminFee", - "type": "uint256" - } - ], - "name": "AdminFeeChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - }, - { - "indexed": false, - "internalType": "bytes32", - "name": "rootHash", - "type": "bytes32" - }, - { - "indexed": false, - "internalType": "string", - "name": "ipfsHash", - "type": "string" - } - ], - "name": "NewCommit", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "newOperator", - "type": "address" - } - ], - "name": "OperatorChanged", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "constant": true, - "inputs": [], - "name": "adminFee", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "blockFreezeSeconds", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "blockTimestamp", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "bytes32", - "name": "leafHash", - "type": "bytes32" - }, - { - "internalType": "bytes32[]", - "name": "others", - "type": "bytes32[]" - } - ], - "name": "calculateRootHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "root", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "pure", - "type": "function" - }, - { - "constant": false, - "inputs": [], - "name": "claimOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - }, - { - "internalType": "bytes32", - "name": "rootHash", - "type": "bytes32" - }, - { - "internalType": "string", - "name": "ipfsHash", - "type": "string" - } - ], - "name": "commit", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "name": "committedHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "earnings", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "joinPartStream", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "operator", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "pendingOwner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - }, - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "proofIsCorrect", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - }, - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint256", - "name": "balance", - "type": "uint256" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "prove", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "address", - "name": "signer", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "signature", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "totalEarnings", - "type": "uint256" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "proveAndWithdrawToSigned", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "newAdminFee", - "type": "uint256" - } - ], - "name": "setAdminFee", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "newOperator", - "type": "address" - } - ], - "name": "setOperator", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "address", - "name": "signer", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "signature", - "type": "bytes" - } - ], - "name": "signatureIsValid", - "outputs": [ - { - "internalType": "bool", - "name": "isValid", - "type": "bool" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "token", - "outputs": [ - { - "internalType": "contract IERC20", - "name": "", - "type": "address" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalProven", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "totalWithdrawn", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [], - "name": "version", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "withdraw", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "totalEarnings", - "type": "uint256" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "withdrawAll", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "totalEarnings", - "type": "uint256" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "withdrawAllFor", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "totalEarnings", - "type": "uint256" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "withdrawAllTo", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "address", - "name": "signer", - "type": "address" - }, - { - "internalType": "bytes", - "name": "signature", - "type": "bytes" - }, - { - "internalType": "uint256", - "name": "blockNumber", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "totalEarnings", - "type": "uint256" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "withdrawAllToSigned", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "withdrawFor", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "withdrawTo", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": false, - "inputs": [ - { - "internalType": "address", - "name": "recipient", - "type": "address" - }, - { - "internalType": "address", - "name": "signer", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "signature", - "type": "bytes" - } - ], - "name": "withdrawToSigned", - "outputs": [], - "payable": false, - "stateMutability": "nonpayable", - "type": "function" - }, - { - "constant": true, - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "name": "withdrawn", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "payable": false, - "stateMutability": "view", - "type": "function" - } - ], - "bytecode": "0x60806040526001600d553480156200001657600080fd5b506040516200333838038062003338833981810160405260a08110156200003c57600080fd5b8101908080519060200190929190805160405193929190846401000000008211156200006757600080fd5b838201915060208201858111156200007e57600080fd5b82518660018202830111640100000000821117156200009c57600080fd5b8083526020830192505050908051906020019080838360005b83811015620000d2578082015181840152602081019050620000b5565b50505050905090810190601f168015620001005780820380516001836020036101000a031916815260200191505b5060405260200180519060200190929190805190602001909291908051906020019092919050505082828233600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508160038190555082600760006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555033600560006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555062000206816200023e60201b60201c565b5050506200021a85620003c460201b60201c565b83600c9080519060200190620002329291906200050f565b505050505050620005be565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161462000302576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600f8152602001807f6572726f725f6f6e6c794f776e6572000000000000000000000000000000000081525060200191505060405180910390fd5b670de0b6b3a764000081111562000381576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600e8152602001807f6572726f725f61646d696e46656500000000000000000000000000000000000081525060200191505060405180910390fd5b806006819055507f11a80b766155f9b8f16a7da44d66269fd694cb1c247f4814244586f68dd534876006546040518082815260200191505060405180910390a150565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161462000488576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600f8152602001807f6572726f725f6f6e6c794f776e6572000000000000000000000000000000000081525060200191505060405180910390fd5b80600560006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff167f4721129e0e676ed6a92909bb24e853ccdd63ad72280cc2e974e38e480e0e6e5460405160405180910390a250565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200055257805160ff191683800117855562000583565b8280016001018555821562000583579182015b828111156200058257825182559160200191906001019062000565565b5b50905062000592919062000596565b5090565b620005bb91905b80821115620005b75760008160009055506001016200059d565b5090565b90565b612d6a80620005ce6000396000f3fe608060405234801561001057600080fd5b50600436106101f05760003560e01c80638beb60b61161010f578063b74d784e116100a2578063e1a9888e11610071578063e1a9888e14610fc3578063e30c397814611005578063f2fde38b1461104f578063fc0c546a14611093576101f0565b8063b74d784e14610dc6578063c3ac610d14610de4578063cb9b51c814610e71578063db518db214610f75576101f0565b8063a2d3cf4b116100de578063a2d3cf4b14610a3e578063adc4c87414610b5b578063b3ab15fb14610c60578063b619d3bd14610ca4576101f0565b80638beb60b61461087c5780638da5cb5b146108aa578063a0b9d101146108f4578063a0be06f914610a20576101f0565b8063543fd313116101875780636dd4a7c9116101565780636dd4a7c9146106ee5780636ef61092146107c457806371b150131461081c57806373d8903b1461083a576101f0565b8063543fd3131461054257806354fd4d501461059a578063570ca735146105b85780636c7a9d2414610602576101f0565b80634942e4cf116101c35780634942e4cf1461040a5780634b319713146104975780634beb62b7146104b55780634e71e0c814610538576101f0565b8063205c2878146101f5578063270231cc146102435780632e1a7d4d146102f057806332ef2b241461031e575b600080fd5b6102416004803603604081101561020b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506110dd565b005b6102ee6004803603608081101561025957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919080359060200190929190803590602001906401000000008111156102aa57600080fd5b8201836020820111156102bc57600080fd5b803590602001918460208302840111640100000000831117156102de57600080fd5b90919293919293905050506110ec565b005b61031c6004803603602081101561030657600080fd5b81019080803590602001909291905050506111a1565b005b6104086004803603608081101561033457600080fd5b8101908080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561038557600080fd5b82018360208201111561039757600080fd5b803590602001918460208302840111640100000000831117156103b957600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f8201169050808301925050505050505091929192905050506111af565b005b6104956004803603606081101561042057600080fd5b8101908080359060200190929190803590602001909291908035906020019064010000000081111561045157600080fd5b82018360208201111561046357600080fd5b8035906020019184602083028401116401000000008311171561048557600080fd5b909192939192939050505061123e565b005b61049f611291565b6040518082815260200191505060405180910390f35b6104bd611297565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156104fd5780820151818401526020810190506104e2565b50505050905090810190601f16801561052a5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610540611335565b005b6105846004803603602081101561055857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061153d565b6040518082815260200191505060405180910390f35b6105a2611555565b6040518082815260200191505060405180910390f35b6105c061155b565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6106ec6004803603608081101561061857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001909291908035906020019064010000000081111561066957600080fd5b82018360208201111561067b57600080fd5b8035906020019184602083028401116401000000008311171561069d57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050509192919290505050611581565b005b6107ae6004803603604081101561070457600080fd5b81019080803590602001909291908035906020019064010000000081111561072b57600080fd5b82018360208201111561073d57600080fd5b8035906020019184602083028401116401000000008311171561075f57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f8201169050808301925050505050505091929192905050506115f4565b6040518082815260200191505060405180910390f35b610806600480360360208110156107da57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506116ab565b6040518082815260200191505060405180910390f35b6108246116c3565b6040518082815260200191505060405180910390f35b6108666004803603602081101561085057600080fd5b81019080803590602001909291905050506116c9565b6040518082815260200191505060405180910390f35b6108a86004803603602081101561089257600080fd5b81019080803590602001909291905050506116e1565b005b6108b2611865565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b610a1e600480360360e081101561090a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561097157600080fd5b82018360208201111561098357600080fd5b803590602001918460018302840111640100000000831117156109a557600080fd5b90919293919293908035906020019092919080359060200190929190803590602001906401000000008111156109da57600080fd5b8201836020820111156109ec57600080fd5b80359060200191846020830284011164010000000083111715610a0e57600080fd5b909192939192939050505061188b565b005b610a286119b0565b6040518082815260200191505060405180910390f35b610b4160048036036080811015610a5457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919080359060200190640100000000811115610abb57600080fd5b820183602082011115610acd57600080fd5b80359060200191846001830284011164010000000083111715610aef57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506119b6565b604051808215151515815260200191505060405180910390f35b610c5e60048036036080811015610b7157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919080359060200190640100000000811115610bd857600080fd5b820183602082011115610bea57600080fd5b80359060200191846001830284011164010000000083111715610c0c57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050611c97565b005b610ca260048036036020811015610c7657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611d26565b005b610dc4600480360360c0811015610cba57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610d1757600080fd5b820183602082011115610d2957600080fd5b80359060200191846001830284011164010000000083111715610d4b57600080fd5b9091929391929390803590602001909291908035906020019092919080359060200190640100000000811115610d8057600080fd5b820183602082011115610d9257600080fd5b80359060200191846020830284011164010000000083111715610db457600080fd5b9091929391929390505050611e70565b005b610dce611fec565b6040518082815260200191505060405180910390f35b610e6f60048036036060811015610dfa57600080fd5b81019080803590602001909291908035906020019092919080359060200190640100000000811115610e2b57600080fd5b820183602082011115610e3d57600080fd5b80359060200191846001830284011164010000000083111715610e5f57600080fd5b9091929391929390505050611ff2565b005b610f5b60048036036080811015610e8757600080fd5b8101908080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919080359060200190640100000000811115610ed857600080fd5b820183602082011115610eea57600080fd5b80359060200191846020830284011164010000000083111715610f0c57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050919291929050505061219a565b604051808215151515815260200191505060405180910390f35b610fc160048036036040811015610f8b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506122ad565b005b610fef60048036036020811015610fd957600080fd5b81019080803590602001909291905050506122bc565b6040518082815260200191505060405180910390f35b61100d6122d4565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6110916004803603602081101561106557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506122fa565b005b61109b612401565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6110e8823383612427565b5050565b611139843385858580806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050506111af565b600061118d600b60003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548561276390919063ffffffff16565b905061119986826110dd565b505050505050565b6111ac333383612427565b50565b6111bb8484848461219a565b61122d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600b8152602001807f6572726f725f70726f6f6600000000000000000000000000000000000000000081525060200191505060405180910390fd5b6112388484846127ad565b50505050565b61128b338585858580806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050611581565b50505050565b60085481565b600c8054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561132d5780601f106113025761010080835404028352916020019161132d565b820191906000526020600020905b81548152906001019060200180831161131057829003601f168201915b505050505081565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146113f8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f6572726f725f6f6e6c7950656e64696e674f776e65720000000000000000000081525060200191505060405180910390fd5b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b600a6020528060005260406000206000915090505481565b600d5481565b600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b61158d838584846111af565b60006115e1600b60008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548461276390919063ffffffff16565b90506115ed85826122ad565b5050505050565b600082905060008090505b82518160ff1610156116a4576000838260ff168151811061161c57fe5b60200260200101519050808310156116645782816040516020018083815260200182815260200192505050604051602081830303815290604052805190602001209250611696565b808360405160200180838152602001828152602001925050506040516020818303038152906040528051906020012092505b5080806001019150506115ff565b5092915050565b600b6020528060005260406000206000915090505481565b60035481565b60046020528060005260406000206000915090505481565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146117a4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600f8152602001807f6572726f725f6f6e6c794f776e6572000000000000000000000000000000000081525060200191505060405180910390fd5b670de0b6b3a7640000811115611822576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600e8152602001807f6572726f725f61646d696e46656500000000000000000000000000000000000081525060200191505060405180910390fd5b806006819055507f11a80b766155f9b8f16a7da44d66269fd694cb1c247f4814244586f68dd534876006546040518082815260200191505060405180910390a150565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6118db89898989898080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506119b6565b61194d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f6572726f725f6261645369676e6174757265000000000000000000000000000081525060200191505060405180910390fd5b61199a848985858580806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050506111af565b6119a5898989612427565b505050505050505050565b60065481565b60006041825114611a2f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f6572726f725f6261645369676e61747572654c656e677468000000000000000081525060200191505060405180910390fd5b60008060006020850151925060408501519150606085015160001a9050601b8160ff161015611a5f57601b810190505b601b8160ff161480611a745750601c8160ff16145b611ae6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260198152602001807f6572726f725f6261645369676e617475726556657273696f6e0000000000000081525060200191505060405180910390fd5b6000888730600b60008c73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205460405160200180807f19457468657265756d205369676e6564204d6573736167653a0a313034000000815250601d018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018481526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001945050505050604051602081830303815290604052805190602001209050600060018284878760405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015611c4d573d6000803e3d6000fd5b5050506020604051035190508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161495505050505050949350505050565b611ca3848484846119b6565b611d15576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f6572726f725f6261645369676e6174757265000000000000000000000000000081525060200191505060405180910390fd5b611d20848484612427565b50505050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611de9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600f8152602001807f6572726f725f6f6e6c794f776e6572000000000000000000000000000000000081525060200191505060405180910390fd5b80600560006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff167f4721129e0e676ed6a92909bb24e853ccdd63ad72280cc2e974e38e480e0e6e5460405160405180910390a250565b611ec18888600089898080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506119b6565b611f33576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f6572726f725f6261645369676e6174757265000000000000000000000000000081525060200191505060405180910390fd5b611f80848885858580806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050506111af565b6000611fd4600b60008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548561276390919063ffffffff16565b9050611fe1898983612427565b505050505050505050565b60095481565b6000801b600080868152602001908152602001600020541461207c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600f8152602001807f6572726f725f6f7665727772697465000000000000000000000000000000000081525060200191505060405180910390fd5b606082828080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505090506120d0858583612b0d565b83600080878152602001908152602001600020819055507f444dcb3cce8fbb3e1dabd2ff958f17fb1e673b759824d631d9cda0690d031eb98585836040518084815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561215757808201518184015260208101905061213c565b50505050905090810190601f1680156121845780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a15050505050565b600080848487604051602001808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401838152602001828152602001935050505060405160208183030381529060405280519060200120905060008060008881526020019081526020016000205490506000801b811415612295576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260138152602001807f6572726f725f626c6f636b4e6f74466f756e640000000000000000000000000081525060200191505060405180910390fd5b61229f82856115f4565b811492505050949350505050565b6122b8828383612427565b5050565b60006020528060005260406000206000915090505481565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146123bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600f8152602001807f6572726f725f6f6e6c794f776e6572000000000000000000000000000000000081525060200191505060405180910390fd5b80600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600760009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000811161249d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f6572726f725f7a65726f5769746864726177000000000000000000000000000081525060200191505060405180910390fd5b60006124f182600b60008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054612bed90919063ffffffff16565b9050600a60008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548111156125a8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600f8152602001807f6572726f725f6f7665726472616674000000000000000000000000000000000081525060200191505060405180910390fd5b80600b60008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061260182600854612bed90919063ffffffff16565b600881905550600760009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb85846040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156126b057600080fd5b505af11580156126c4573d6000803e3d6000fd5b505050506040513d60208110156126da57600080fd5b810190808051906020019092919050505061275d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600e8152602001807f6572726f725f7472616e7366657200000000000000000000000000000000000081525060200191505060405180910390fd5b50505050565b60006127a583836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250612c75565b905092915050565b6000600460008581526020019081526020016000205490506003548101421161283e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600c8152602001807f6572726f725f66726f7a656e000000000000000000000000000000000000000081525060200191505060405180910390fd5b81600a60008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054106128f2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260118152602001807f6572726f725f6f6c644561726e696e677300000000000000000000000000000081525060200191505060405180910390fd5b612958600a60008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461294a84600954612bed90919063ffffffff16565b61276390919063ffffffff16565b600981905550600760009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b1580156129fd57600080fd5b505afa158015612a11573d6000803e3d6000fd5b505050506040513d6020811015612a2757600080fd5b8101908080519060200190929190505050612a4f60085460095461276390919063ffffffff16565b1115612ac3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f6572726f725f6d697373696e6742616c616e636500000000000000000000000081525060200191505060405180910390fd5b81600a60008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555050505050565b600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614612bd0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f6572726f725f6e6f745065726d6974746564000000000000000000000000000081525060200191505060405180910390fd5b426004600085815260200190815260200160002081905550505050565b600080828401905083811015612c6b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b6000838311158290612d22576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b83811015612ce7578082015181840152602081019050612ccc565b50505050905090810190601f168015612d145780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b506000838503905080915050939250505056fea265627a7a72315820c41307a82520f0f3c5d150a03d5d08db63f386017b9d416825cd59dd315625f564736f6c63430005100032", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101f05760003560e01c80638beb60b61161010f578063b74d784e116100a2578063e1a9888e11610071578063e1a9888e14610fc3578063e30c397814611005578063f2fde38b1461104f578063fc0c546a14611093576101f0565b8063b74d784e14610dc6578063c3ac610d14610de4578063cb9b51c814610e71578063db518db214610f75576101f0565b8063a2d3cf4b116100de578063a2d3cf4b14610a3e578063adc4c87414610b5b578063b3ab15fb14610c60578063b619d3bd14610ca4576101f0565b80638beb60b61461087c5780638da5cb5b146108aa578063a0b9d101146108f4578063a0be06f914610a20576101f0565b8063543fd313116101875780636dd4a7c9116101565780636dd4a7c9146106ee5780636ef61092146107c457806371b150131461081c57806373d8903b1461083a576101f0565b8063543fd3131461054257806354fd4d501461059a578063570ca735146105b85780636c7a9d2414610602576101f0565b80634942e4cf116101c35780634942e4cf1461040a5780634b319713146104975780634beb62b7146104b55780634e71e0c814610538576101f0565b8063205c2878146101f5578063270231cc146102435780632e1a7d4d146102f057806332ef2b241461031e575b600080fd5b6102416004803603604081101561020b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506110dd565b005b6102ee6004803603608081101561025957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919080359060200190929190803590602001906401000000008111156102aa57600080fd5b8201836020820111156102bc57600080fd5b803590602001918460208302840111640100000000831117156102de57600080fd5b90919293919293905050506110ec565b005b61031c6004803603602081101561030657600080fd5b81019080803590602001909291905050506111a1565b005b6104086004803603608081101561033457600080fd5b8101908080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561038557600080fd5b82018360208201111561039757600080fd5b803590602001918460208302840111640100000000831117156103b957600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f8201169050808301925050505050505091929192905050506111af565b005b6104956004803603606081101561042057600080fd5b8101908080359060200190929190803590602001909291908035906020019064010000000081111561045157600080fd5b82018360208201111561046357600080fd5b8035906020019184602083028401116401000000008311171561048557600080fd5b909192939192939050505061123e565b005b61049f611291565b6040518082815260200191505060405180910390f35b6104bd611297565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156104fd5780820151818401526020810190506104e2565b50505050905090810190601f16801561052a5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b610540611335565b005b6105846004803603602081101561055857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919050505061153d565b6040518082815260200191505060405180910390f35b6105a2611555565b6040518082815260200191505060405180910390f35b6105c061155b565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6106ec6004803603608081101561061857600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190803590602001909291908035906020019064010000000081111561066957600080fd5b82018360208201111561067b57600080fd5b8035906020019184602083028401116401000000008311171561069d57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050509192919290505050611581565b005b6107ae6004803603604081101561070457600080fd5b81019080803590602001909291908035906020019064010000000081111561072b57600080fd5b82018360208201111561073d57600080fd5b8035906020019184602083028401116401000000008311171561075f57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f8201169050808301925050505050505091929192905050506115f4565b6040518082815260200191505060405180910390f35b610806600480360360208110156107da57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506116ab565b6040518082815260200191505060405180910390f35b6108246116c3565b6040518082815260200191505060405180910390f35b6108666004803603602081101561085057600080fd5b81019080803590602001909291905050506116c9565b6040518082815260200191505060405180910390f35b6108a86004803603602081101561089257600080fd5b81019080803590602001909291905050506116e1565b005b6108b2611865565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b610a1e600480360360e081101561090a57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291908035906020019064010000000081111561097157600080fd5b82018360208201111561098357600080fd5b803590602001918460018302840111640100000000831117156109a557600080fd5b90919293919293908035906020019092919080359060200190929190803590602001906401000000008111156109da57600080fd5b8201836020820111156109ec57600080fd5b80359060200191846020830284011164010000000083111715610a0e57600080fd5b909192939192939050505061188b565b005b610a286119b0565b6040518082815260200191505060405180910390f35b610b4160048036036080811015610a5457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919080359060200190640100000000811115610abb57600080fd5b820183602082011115610acd57600080fd5b80359060200191846001830284011164010000000083111715610aef57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506119b6565b604051808215151515815260200191505060405180910390f35b610c5e60048036036080811015610b7157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919080359060200190640100000000811115610bd857600080fd5b820183602082011115610bea57600080fd5b80359060200191846001830284011164010000000083111715610c0c57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050611c97565b005b610ca260048036036020811015610c7657600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050611d26565b005b610dc4600480360360c0811015610cba57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190640100000000811115610d1757600080fd5b820183602082011115610d2957600080fd5b80359060200191846001830284011164010000000083111715610d4b57600080fd5b9091929391929390803590602001909291908035906020019092919080359060200190640100000000811115610d8057600080fd5b820183602082011115610d9257600080fd5b80359060200191846020830284011164010000000083111715610db457600080fd5b9091929391929390505050611e70565b005b610dce611fec565b6040518082815260200191505060405180910390f35b610e6f60048036036060811015610dfa57600080fd5b81019080803590602001909291908035906020019092919080359060200190640100000000811115610e2b57600080fd5b820183602082011115610e3d57600080fd5b80359060200191846001830284011164010000000083111715610e5f57600080fd5b9091929391929390505050611ff2565b005b610f5b60048036036080811015610e8757600080fd5b8101908080359060200190929190803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919080359060200190640100000000811115610ed857600080fd5b820183602082011115610eea57600080fd5b80359060200191846020830284011164010000000083111715610f0c57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050919291929050505061219a565b604051808215151515815260200191505060405180910390f35b610fc160048036036040811015610f8b57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803590602001909291905050506122ad565b005b610fef60048036036020811015610fd957600080fd5b81019080803590602001909291905050506122bc565b6040518082815260200191505060405180910390f35b61100d6122d4565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6110916004803603602081101561106557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291905050506122fa565b005b61109b612401565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6110e8823383612427565b5050565b611139843385858580806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050506111af565b600061118d600b60003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548561276390919063ffffffff16565b905061119986826110dd565b505050505050565b6111ac333383612427565b50565b6111bb8484848461219a565b61122d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600b8152602001807f6572726f725f70726f6f6600000000000000000000000000000000000000000081525060200191505060405180910390fd5b6112388484846127ad565b50505050565b61128b338585858580806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f82011690508083019250505050505050611581565b50505050565b60085481565b600c8054600181600116156101000203166002900480601f01602080910402602001604051908101604052809291908181526020018280546001816001161561010002031660029004801561132d5780601f106113025761010080835404028352916020019161132d565b820191906000526020600020905b81548152906001019060200180831161131057829003601f168201915b505050505081565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146113f8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260168152602001807f6572726f725f6f6e6c7950656e64696e674f776e65720000000000000000000081525060200191505060405180910390fd5b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a3600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550565b600a6020528060005260406000206000915090505481565b600d5481565b600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b61158d838584846111af565b60006115e1600b60008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548461276390919063ffffffff16565b90506115ed85826122ad565b5050505050565b600082905060008090505b82518160ff1610156116a4576000838260ff168151811061161c57fe5b60200260200101519050808310156116645782816040516020018083815260200182815260200192505050604051602081830303815290604052805190602001209250611696565b808360405160200180838152602001828152602001925050506040516020818303038152906040528051906020012092505b5080806001019150506115ff565b5092915050565b600b6020528060005260406000206000915090505481565b60035481565b60046020528060005260406000206000915090505481565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146117a4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600f8152602001807f6572726f725f6f6e6c794f776e6572000000000000000000000000000000000081525060200191505060405180910390fd5b670de0b6b3a7640000811115611822576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600e8152602001807f6572726f725f61646d696e46656500000000000000000000000000000000000081525060200191505060405180910390fd5b806006819055507f11a80b766155f9b8f16a7da44d66269fd694cb1c247f4814244586f68dd534876006546040518082815260200191505060405180910390a150565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6118db89898989898080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506119b6565b61194d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f6572726f725f6261645369676e6174757265000000000000000000000000000081525060200191505060405180910390fd5b61199a848985858580806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050506111af565b6119a5898989612427565b505050505050505050565b60065481565b60006041825114611a2f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260188152602001807f6572726f725f6261645369676e61747572654c656e677468000000000000000081525060200191505060405180910390fd5b60008060006020850151925060408501519150606085015160001a9050601b8160ff161015611a5f57601b810190505b601b8160ff161480611a745750601c8160ff16145b611ae6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260198152602001807f6572726f725f6261645369676e617475726556657273696f6e0000000000000081525060200191505060405180910390fd5b6000888730600b60008c73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205460405160200180807f19457468657265756d205369676e6564204d6573736167653a0a313034000000815250601d018573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b81526014018481526020018373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401828152602001945050505050604051602081830303815290604052805190602001209050600060018284878760405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015611c4d573d6000803e3d6000fd5b5050506020604051035190508873ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161495505050505050949350505050565b611ca3848484846119b6565b611d15576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f6572726f725f6261645369676e6174757265000000000000000000000000000081525060200191505060405180910390fd5b611d20848484612427565b50505050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611de9576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600f8152602001807f6572726f725f6f6e6c794f776e6572000000000000000000000000000000000081525060200191505060405180910390fd5b80600560006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff167f4721129e0e676ed6a92909bb24e853ccdd63ad72280cc2e974e38e480e0e6e5460405160405180910390a250565b611ec18888600089898080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050506119b6565b611f33576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f6572726f725f6261645369676e6174757265000000000000000000000000000081525060200191505060405180910390fd5b611f80848885858580806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f820116905080830192505050505050506111af565b6000611fd4600b60008a73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548561276390919063ffffffff16565b9050611fe1898983612427565b505050505050505050565b60095481565b6000801b600080868152602001908152602001600020541461207c576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600f8152602001807f6572726f725f6f7665727772697465000000000000000000000000000000000081525060200191505060405180910390fd5b606082828080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505090506120d0858583612b0d565b83600080878152602001908152602001600020819055507f444dcb3cce8fbb3e1dabd2ff958f17fb1e673b759824d631d9cda0690d031eb98585836040518084815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561215757808201518184015260208101905061213c565b50505050905090810190601f1680156121845780820380516001836020036101000a031916815260200191505b5094505050505060405180910390a15050505050565b600080848487604051602001808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1660601b8152601401838152602001828152602001935050505060405160208183030381529060405280519060200120905060008060008881526020019081526020016000205490506000801b811415612295576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260138152602001807f6572726f725f626c6f636b4e6f74466f756e640000000000000000000000000081525060200191505060405180910390fd5b61229f82856115f4565b811492505050949350505050565b6122b8828383612427565b5050565b60006020528060005260406000206000915090505481565b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146123bd576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600f8152602001807f6572726f725f6f6e6c794f776e6572000000000000000000000000000000000081525060200191505060405180910390fd5b80600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050565b600760009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b6000811161249d576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f6572726f725f7a65726f5769746864726177000000000000000000000000000081525060200191505060405180910390fd5b60006124f182600b60008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054612bed90919063ffffffff16565b9050600a60008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020548111156125a8576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600f8152602001807f6572726f725f6f7665726472616674000000000000000000000000000000000081525060200191505060405180910390fd5b80600b60008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061260182600854612bed90919063ffffffff16565b600881905550600760009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb85846040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156126b057600080fd5b505af11580156126c4573d6000803e3d6000fd5b505050506040513d60208110156126da57600080fd5b810190808051906020019092919050505061275d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600e8152602001807f6572726f725f7472616e7366657200000000000000000000000000000000000081525060200191505060405180910390fd5b50505050565b60006127a583836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250612c75565b905092915050565b6000600460008581526020019081526020016000205490506003548101421161283e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252600c8152602001807f6572726f725f66726f7a656e000000000000000000000000000000000000000081525060200191505060405180910390fd5b81600a60008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054106128f2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260118152602001807f6572726f725f6f6c644561726e696e677300000000000000000000000000000081525060200191505060405180910390fd5b612958600a60008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461294a84600954612bed90919063ffffffff16565b61276390919063ffffffff16565b600981905550600760009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b8152600401808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060206040518083038186803b1580156129fd57600080fd5b505afa158015612a11573d6000803e3d6000fd5b505050506040513d6020811015612a2757600080fd5b8101908080519060200190929190505050612a4f60085460095461276390919063ffffffff16565b1115612ac3576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260148152602001807f6572726f725f6d697373696e6742616c616e636500000000000000000000000081525060200191505060405180910390fd5b81600a60008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555050505050565b600560009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614612bd0576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260128152602001807f6572726f725f6e6f745065726d6974746564000000000000000000000000000081525060200191505060405180910390fd5b426004600085815260200190815260200160002081905550505050565b600080828401905083811015612c6b576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252601b8152602001807f536166654d6174683a206164646974696f6e206f766572666c6f77000000000081525060200191505060405180910390fd5b8091505092915050565b6000838311158290612d22576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825283818151815260200191508051906020019080838360005b83811015612ce7578082015181840152602081019050612ccc565b50505050905090810190601f168015612d145780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b506000838503905080915050939250505056fea265627a7a72315820c41307a82520f0f3c5d150a03d5d08db63f386017b9d416825cd59dd315625f564736f6c63430005100032", - "sourceMap": "73:483:0:-;;;247:1;225:23;;255:299;8:9:-1;5:2;;;30:1;27;20:12;5:2;255:299:0;;;;;;;;;;;;;;;13:3:-1;8;5:12;2:2;;;30:1;27;20:12;2:2;255:299:0;;;;;;;;;;;;;;;;;;;;;;19:11:-1;14:3;11:20;8:2;;;44:1;41;34:12;8:2;71:11;66:3;62:21;55:28;;123:4;118:3;114:14;159:9;141:16;138:31;135:2;;;182:1;179;172:12;135:2;219:3;213:10;330:9;325:1;311:12;307:20;289:16;285:43;282:58;261:11;247:12;244:29;233:115;230:2;;;361:1;358;351:12;230:2;384:12;379:3;372:25;420:4;415:3;411:14;404:21;;0:432;;255:299:0;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;99:1;94:3;90:11;84:18;80:1;75:3;71:11;64:39;52:2;49:1;45:10;40:15;;8:100;;;12:14;255:299:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;408:12;422:24;448:16;574:10:4;566:5;;:18;;;;;;;;;;;;;;;;;;2938:24:3;2917:18;:45;;;;2987:12;2972:5;;:28;;;;;;;;;;;;;;;;;;3021:10;3010:8;;:21;;;;;;;;;;;;;;;;;;3041:28;3053:15;3041:11;;;:28;;:::i;:::-;2813:263;;;483:21:0;495:8;483:11;;;:21;;:::i;:::-;531:16;514:14;:33;;;;;;;;;;;;:::i;:::-;;255:299;;;;;73:483;;3608:194:3;732:5:4;;;;;;;;;;;718:19;;:10;:19;;;710:47;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3697:7:3;3682:11;:22;;3674:49;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3744:11;3733:8;:22;;;;3770:25;3786:8;;3770:25;;;;;;;;;;;;;;;;;;3608:194;:::o;3212:141::-;732:5:4;;;;;;;;;;;718:19;;:10;:19;;;710:47;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3292:11:3;3281:8;;:22;;;;;;;;;;;;;;;;;;3334:11;3318:28;;;;;;;;;;;;3212:141;:::o;73:483:0:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;;:::o;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;:::-;;;;;;;", - "deployedSourceMap": "73:483:0:-;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;73:483:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;11565:116:3;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;11565:116:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;7426:299;;;;;;13:3:-1;8;5:12;2:2;;;30:1;27;20:12;2:2;7426:299:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;7426:299:3;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;7426:299:3;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;39:11;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;7426:299:3;;;;;;;;;;;;:::i;:::-;;10712:96;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;10712:96:3;;;;;;;;;;;;;;;;;:::i;:::-;;3724:244:2;;;;;;13:3:-1;8;5:12;2:2;;;30:1;27;20:12;2:2;3724:244:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;3724:244:2;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;3724:244:2;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;39:11;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;3724:244:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;3724:244:2;;;;;;;;;;;;;;;:::i;:::-;;6029:172:3;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;6029:172:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;6029:172:3;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;6029:172:3;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;39:11;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;6029:172:3;;;;;;;;;;;;:::i;:::-;;1899:26;;;:::i;:::-;;;;;;;;;;;;;;;;;;;118:28:0;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;99:1;94:3;90:11;84:18;80:1;75:3;71:11;64:39;52:2;49:1;45:10;40:15;;8:100;;;12:14;118:28:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1122:232:4;;;:::i;:::-;;2667:41:3;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;2667:41:3;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;225:23:0;;;:::i;:::-;;;;;;;;;;;;;;;;;;;1715::3;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;6679:295;;;;;;13:3:-1;8;5:12;2:2;;;30:1;27;20:12;2:2;6679:295:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;6679:295:3;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;6679:295:3;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;39:11;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;6679:295:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;6679:295:3;;;;;;;;;;;;;;;:::i;:::-;;4937:562:2;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;4937:562:2;;;;;;;;;;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;4937:562:2;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;4937:562:2;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;39:11;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;4937:562:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;4937:562:2;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;2764:42:3;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;2764:42:3;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;1267:30;;;:::i;:::-;;;;;;;;;;;;;;;;;;;1594:44;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;1594:44:3;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;3608:194;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;3608:194:3;;;;;;;;;;;;;;;;;:::i;:::-;;239:20:4;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;10079:481:3;;;;;;13:3:-1;8;5:12;2:2;;;30:1;27;20:12;2:2;10079:481:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;10079:481:3;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;10079:481:3;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;39:11;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;10079:481:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;10079:481:3;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;10079:481:3;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;39:11;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;10079:481:3;;;;;;;;;;;;:::i;:::-;;1783:20;;;:::i;:::-;;;;;;;;;;;;;;;;;;;14582:961;;;;;;13:3:-1;8;5:12;2:2;;;30:1;27;20:12;2:2;14582:961:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;14582:961:3;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;14582:961:3;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;39:11;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;14582:961:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;14582:961:3;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;12199:253;;;;;;13:3:-1;8;5:12;2:2;;;30:1;27;20:12;2:2;12199:253:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;12199:253:3;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;12199:253:3;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;39:11;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;12199:253:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;12199:253:3;;;;;;;;;;;;;;;:::i;:::-;;3212:141;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;3212:141:3;;;;;;;;;;;;;;;;;;;:::i;:::-;;8712:543;;;;;;13:3:-1;8;5:12;2:2;;;30:1;27;20:12;2:2;8712:543:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;8712:543:3;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;8712:543:3;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;39:11;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;8712:543:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;8712:543:3;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;8712:543:3;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;39:11;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;8712:543:3;;;;;;;;;;;;:::i;:::-;;2585:23;;;:::i;:::-;;;;;;;;;;;;;;;;;;;2973:402:2;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;2973:402:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;2973:402:2;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;2973:402:2;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;39:11;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;2973:402:2;;;;;;;;;;;;:::i;:::-;;4342:392;;;;;;13:3:-1;8;5:12;2:2;;;30:1;27;20:12;2:2;4342:392:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;21:11:-1;8;5:28;2:2;;;46:1;43;36:12;2:2;4342:392:2;;35:9:-1;28:4;12:14;8:25;5:40;2:2;;;58:1;55;48:12;2:2;4342:392:2;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;39:11;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;4342:392:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;4342:392:2;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;11118:116:3;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;11118:116:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;;1919:46:2;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;1919:46:2;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;265:27:4;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;928:102;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;928:102:4;;;;;;;;;;;;;;;;;;;:::i;:::-;;1810:19:3;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;11565:116;11634:40;11644:9;11655:10;11667:6;11634:9;:40::i;:::-;11565:116;;:::o;7426:299::-;7551:52;7557:11;7570:10;7582:13;7597:5;;7551:52;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;7551:52:3;;;;;;:5;:52::i;:::-;7613:17;7633:40;7651:9;:21;7661:10;7651:21;;;;;;;;;;;;;;;;7633:13;:17;;:40;;;;:::i;:::-;7613:60;;7683:35;7694:9;7705:12;7683:10;:35::i;:::-;7426:299;;;;;;:::o;10712:96::-;10760:41;10770:10;10782;10794:6;10760:9;:41::i;:::-;10712:96;:::o;3724:244:2:-;3837:52;3852:11;3865:7;3874;3883:5;3837:14;:52::i;:::-;3829:76;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3915:46;3931:11;3944:7;3953;3915:15;:46::i;:::-;3724:244;;;;:::o;6029:172:3:-;6133:61;6148:10;6160:11;6173:13;6188:5;;6133:61;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;6133:61:3;;;;;;:14;:61::i;:::-;6029:172;;;;:::o;1899:26::-;;;;:::o;118:28:0:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;1122:232:4:-;1187:12;;;;;;;;;;;1173:26;;:10;:26;;;1165:61;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1269:12;;;;;;;;;;;1241:41;;1262:5;;;;;;;;;;;1241:41;;;;;;;;;;;;1300:12;;;;;;;;;;;1292:5;;:20;;;;;;;;;;;;;;;;;;1345:1;1322:12;;:25;;;;;;;;;;;;;;;;;;1122:232::o;2667:41:3:-;;;;;;;;;;;;;;;;;:::o;225:23:0:-;;;;:::o;1715::3:-;;;;;;;;;;;;;:::o;6679:295::-;6801:51;6807:11;6820:9;6831:13;6846:5;6801;:51::i;:::-;6862:17;6882:39;6900:9;:20;6910:9;6900:20;;;;;;;;;;;;;;;;6882:13;:17;;:39;;;;:::i;:::-;6862:59;;6931:36;6943:9;6954:12;6931:11;:36::i;:::-;6679:295;;;;;:::o;4937:562:2:-;5028:12;5059:8;5052:15;;5082:7;5092:1;5082:11;;5077:416;5099:6;:13;5095:1;:17;;;5077:416;;;5133:13;5149:6;5156:1;5149:9;;;;;;;;;;;;;;;;5133:25;;5183:5;5176:4;:12;5172:311;;;5369:4;5375:5;5352:29;;;;;;;;;;;;;;;;;;;;;49:4:-1;39:7;30;26:21;22:32;13:7;6:49;5352:29:2;;;5342:40;;;;;;5335:47;;5172:311;;;5455:5;5462:4;5438:29;;;;;;;;;;;;;;;;;;;;;49:4:-1;39:7;30;26:21;22:32;13:7;6:49;5438:29:2;;;5428:40;;;;;;5421:47;;5172:311;5077:416;5114:3;;;;;;;5077:416;;;;4937:562;;;;:::o;2764:42:3:-;;;;;;;;;;;;;;;;;:::o;1267:30::-;;;;:::o;1594:44::-;;;;;;;;;;;;;;;;;:::o;3608:194::-;732:5:4;;;;;;;;;;;718:19;;:10;:19;;;710:47;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3697:7:3;3682:11;:22;;3674:49;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3744:11;3733:8;:22;;;;3770:25;3786:8;;3770:25;;;;;;;;;;;;;;;;;;3608:194;:::o;239:20:4:-;;;;;;;;;;;;;:::o;10079:481:3:-;10372:54;10389:9;10400:6;10408;10416:9;;10372:54;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;10372:54:3;;;;;;:16;:54::i;:::-;10364:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;10459:48;10465:11;10478:6;10486:13;10501:5;;10459:48;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;10459:48:3;;;;;;:5;:48::i;:::-;10517:36;10527:9;10538:6;10546;10517:9;:36::i;:::-;10079:481;;;;;;;;;:::o;1783:20::-;;;;:::o;14582:961::-;14701:12;14753:2;14733:9;:16;:22;14725:59;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;14795:9;14806;14817:7;14939:2;14928:9;14924:18;14918:25;14913:30;;14982:2;14971:9;14967:18;14961:25;14956:30;;15033:2;15022:9;15018:18;15012:25;15009:1;15004:34;14999:39;;15065:2;15061:1;:6;;;15057:44;;;15088:2;15083:7;;;;15057:44;15123:2;15118:1;:7;;;:18;;;;15134:2;15129:1;:7;;;15118:18;15110:56;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;15272:19;15371:9;15382:6;15398:4;15405:9;:17;15415:6;15405:17;;;;;;;;;;;;;;;;15304:119;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;49:4:-1;39:7;30;26:21;22:32;13:7;6:49;15304:119:3;;;15294:130;;;;;;15272:152;;15434:24;15461:31;15471:11;15484:1;15487;15490;15461:31;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;15461:31:3;;;;;;;;15434:58;;15530:6;15510:26;;:16;:26;;;15503:33;;;;;;;14582:961;;;;;;:::o;12199:253::-;12322:54;12339:9;12350:6;12358;12366:9;12322:16;:54::i;:::-;12314:85;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12409:36;12419:9;12430:6;12438;12409:9;:36::i;:::-;12199:253;;;;:::o;3212:141::-;732:5:4;;;;;;;;;;;718:19;;:10;:19;;;710:47;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3292:11:3;3281:8;;:22;;;;;;;;;;;;;;;;;;3334:11;3318:28;;;;;;;;;;;;3212:141;:::o;8712:543::-;9000:49;9017:9;9028:6;9036:1;9039:9;;9000:49;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;9000:49:3;;;;;;:16;:49::i;:::-;8992:80;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;9082:48;9088:11;9101:6;9109:13;9124:5;;9082:48;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;9082:48:3;;;;;;:5;:48::i;:::-;9140:17;9160:36;9178:9;:17;9188:6;9178:17;;;;;;;;;;;;;;;;9160:13;:17;;:36;;;;:::i;:::-;9140:56;;9206:42;9216:9;9227:6;9235:12;9206:9;:42::i;:::-;8712:543;;;;;;;;;:::o;2585:23::-;;;;:::o;2973:402:2:-;3108:1;3078:31;;:13;:26;3092:11;3078:26;;;;;;;;;;;;:31;3070:59;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3139:19;3161:8;;3139:30;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;93:3;85:6;81:16;74:27;137:4;133:9;126:4;121:3;117:14;113:30;106:37;;169:3;161:6;157:16;147:26;;3139:30:2;;;;;;;;3179:38;3188:11;3201:8;3211:5;3179:8;:38::i;:::-;3306:8;3277:13;:26;3291:11;3277:26;;;;;;;;;;;:37;;;;3329:39;3339:11;3352:8;3362:5;3329:39;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;99:1;94:3;90:11;84:18;80:1;75:3;71:11;64:39;52:2;49:1;45:10;40:15;;8:100;;;12:14;3329:39:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;2973:402;;;;;:::o;4342:392::-;4459:4;4475:16;4521:7;4530;4539:11;4504:47;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;49:4:-1;39:7;30;26:21;22:32;13:7;6:49;4504:47:2;;;4494:58;;;;;;4475:77;;4562:16;4581:13;:26;4595:11;4581:26;;;;;;;;;;;;4562:45;;4637:3;4625:15;;:8;:15;;4617:47;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4693:34;4711:8;4721:5;4693:17;:34::i;:::-;4681:8;:46;4674:53;;;;4342:392;;;;;;:::o;11118:116:3:-;11188:39;11198:9;11209;11220:6;11188:9;:39::i;:::-;11118:116;;:::o;1919:46:2:-;;;;;;;;;;;;;;;;;:::o;265:27:4:-;;;;;;;;;;;;;:::o;928:102::-;732:5;;;;;;;;;;;718:19;;:10;:19;;;710:47;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1015:8;1000:12;;:23;;;;;;;;;;;;;;;;;;928:102;:::o;1810:19:3:-;;;;;;;;;;;;;:::o;12973:399::-;13077:1;13068:6;:10;13060:41;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;13111:6;13120:30;13143:6;13120:9;:18;13130:7;13120:18;;;;;;;;;;;;;;;;:22;;:30;;;;:::i;:::-;13111:39;;13173:8;:17;13182:7;13173:17;;;;;;;;;;;;;;;;13168:1;:22;;13160:50;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;13241:1;13220:9;:18;13230:7;13220:18;;;;;;;;;;;;;;;:22;;;;13269:26;13288:6;13269:14;;:18;;:26;;;;:::i;:::-;13252:14;:43;;;;13313:5;;;;;;;;;;;:14;;;13328:9;13339:6;13313:33;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;13313:33:3;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;13313:33:3;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;13313:33:3;;;;;;;;;;;;;;;;13305:60;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12973:399;;;;:::o;1274:134:8:-;1332:7;1358:43;1362:1;1365;1358:43;;;;;;;;;;;;;;;;;:3;:43::i;:::-;1351:50;;1274:134;;;;:::o;5148:577:3:-;5245:21;5269:14;:27;5284:11;5269:27;;;;;;;;;;;;5245:51;;5339:18;;5320:16;:37;5314:3;:43;5306:68;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5461:11;5441:8;:17;5450:7;5441:17;;;;;;;;;;;;;;;;:31;5433:61;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5518:51;5551:8;:17;5560:7;5551:17;;;;;;;;;;;;;;;;5518:28;5534:11;5518;;:15;;:28;;;;:::i;:::-;:32;;:51;;;;:::i;:::-;5504:11;:65;;;;5622:5;;;;;;;;;;;:15;;;5646:4;5622:30;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;30:1;27;20:12;5:2;5622:30:3;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;5622:30:3;;;;;;;13:2:-1;8:3;5:11;2:2;;;29:1;26;19:12;2:2;5622:30:3;;;;;;;;;;;;;;;;5587:31;5603:14;;5587:11;;:15;;:31;;;;:::i;:::-;:65;;5579:98;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5707:11;5687:8;:17;5696:7;5687:17;;;;;;;;;;;;;;;:31;;;;5148:577;;;;:::o;4084:231::-;4185:8;;;;;;;;;;;4171:22;;:10;:22;;;4163:53;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4256:3;4226:14;:27;4241:11;4226:27;;;;;;;;;;;:33;;;;4084:231;;;:::o;834:176:8:-;892:7;911:9;927:1;923;:5;911:17;;951:1;946;:6;;938:46;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1002:1;995:8;;;834:176;;;;:::o;1732:187::-;1818:7;1850:1;1845;:6;;1853:12;1837:29;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;99:1;94:3;90:11;84:18;80:1;75:3;71:11;64:39;52:2;49:1;45:10;40:15;;8:100;;;12:14;1837:29:8;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1876:9;1892:1;1888;:5;1876:17;;1911:1;1904:8;;;1732:187;;;;;:::o", - "source": "pragma solidity ^0.5.16;\n\nimport \"monoplasma/contracts/Monoplasma.sol\";\n\ncontract DataunionVault is Monoplasma {\n\n string public joinPartStream;\n\n /** Server version. This must be kept in sync with src/server.js */\n uint public version = 1;\n\n constructor(address operator, string memory joinPartStreamId, address tokenAddress, uint blockFreezePeriodSeconds, uint adminFeeFraction)\n Monoplasma(tokenAddress, blockFreezePeriodSeconds, adminFeeFraction) public {\n setOperator(operator);\n joinPartStream = joinPartStreamId;\n }\n}\n", - "sourcePath": "/home/heynow/streamr/data-union-server/contracts/DataunionVault.sol", - "ast": { - "absolutePath": "/home/heynow/streamr/data-union-server/contracts/DataunionVault.sol", - "exportedSymbols": { - "DataunionVault": [ - 37 - ] - }, - "id": 38, - "nodeType": "SourceUnit", - "nodes": [ - { - "id": 1, - "literals": [ - "solidity", - "^", - "0.5", - ".16" - ], - "nodeType": "PragmaDirective", - "src": "0:24:0" - }, - { - "absolutePath": "monoplasma/contracts/Monoplasma.sol", - "file": "monoplasma/contracts/Monoplasma.sol", - "id": 2, - "nodeType": "ImportDirective", - "scope": 38, - "sourceUnit": 872, - "src": "26:45:0", - "symbolAliases": [], - "unitAlias": "" - }, - { - "baseContracts": [ - { - "arguments": null, - "baseName": { - "contractScope": null, - "id": 3, - "name": "Monoplasma", - "nodeType": "UserDefinedTypeName", - "referencedDeclaration": 871, - "src": "100:10:0", - "typeDescriptions": { - "typeIdentifier": "t_contract$_Monoplasma_$871", - "typeString": "contract Monoplasma" - } - }, - "id": 4, - "nodeType": "InheritanceSpecifier", - "src": "100:10:0" - } - ], - "contractDependencies": [ - 276, - 871, - 944 - ], - "contractKind": "contract", - "documentation": null, - "fullyImplemented": true, - "id": 37, - "linearizedBaseContracts": [ - 37, - 871, - 944, - 276 - ], - "name": "DataunionVault", - "nodeType": "ContractDefinition", - "nodes": [ - { - "constant": false, - "id": 6, - "name": "joinPartStream", - "nodeType": "VariableDeclaration", - "scope": 37, - "src": "118:28:0", - "stateVariable": true, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_string_storage", - "typeString": "string" - }, - "typeName": { - "id": 5, - "name": "string", - "nodeType": "ElementaryTypeName", - "src": "118:6:0", - "typeDescriptions": { - "typeIdentifier": "t_string_storage_ptr", - "typeString": "string" - } - }, - "value": null, - "visibility": "public" - }, - { - "constant": false, - "id": 9, - "name": "version", - "nodeType": "VariableDeclaration", - "scope": 37, - "src": "225:23:0", - "stateVariable": true, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - }, - "typeName": { - "id": 7, - "name": "uint", - "nodeType": "ElementaryTypeName", - "src": "225:4:0", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - "value": { - "argumentTypes": null, - "hexValue": "31", - "id": 8, - "isConstant": false, - "isLValue": false, - "isPure": true, - "kind": "number", - "lValueRequested": false, - "nodeType": "Literal", - "src": "247:1:0", - "subdenomination": null, - "typeDescriptions": { - "typeIdentifier": "t_rational_1_by_1", - "typeString": "int_const 1" - }, - "value": "1" - }, - "visibility": "public" - }, - { - "body": { - "id": 35, - "nodeType": "Block", - "src": "473:81:0", - "statements": [ - { - "expression": { - "argumentTypes": null, - "arguments": [ - { - "argumentTypes": null, - "id": 28, - "name": "operator", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 11, - "src": "495:8:0", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - } - ], - "expression": { - "argumentTypes": [ - { - "typeIdentifier": "t_address", - "typeString": "address" - } - ], - "id": 27, - "name": "setOperator", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 366, - "src": "483:11:0", - "typeDescriptions": { - "typeIdentifier": "t_function_internal_nonpayable$_t_address_$returns$__$", - "typeString": "function (address)" - } - }, - "id": 29, - "isConstant": false, - "isLValue": false, - "isPure": false, - "kind": "functionCall", - "lValueRequested": false, - "names": [], - "nodeType": "FunctionCall", - "src": "483:21:0", - "typeDescriptions": { - "typeIdentifier": "t_tuple$__$", - "typeString": "tuple()" - } - }, - "id": 30, - "nodeType": "ExpressionStatement", - "src": "483:21:0" - }, - { - "expression": { - "argumentTypes": null, - "id": 33, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftHandSide": { - "argumentTypes": null, - "id": 31, - "name": "joinPartStream", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 6, - "src": "514:14:0", - "typeDescriptions": { - "typeIdentifier": "t_string_storage", - "typeString": "string storage ref" - } - }, - "nodeType": "Assignment", - "operator": "=", - "rightHandSide": { - "argumentTypes": null, - "id": 32, - "name": "joinPartStreamId", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 13, - "src": "531:16:0", - "typeDescriptions": { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string memory" - } - }, - "src": "514:33:0", - "typeDescriptions": { - "typeIdentifier": "t_string_storage", - "typeString": "string storage ref" - } - }, - "id": 34, - "nodeType": "ExpressionStatement", - "src": "514:33:0" - } - ] - }, - "documentation": null, - "id": 36, - "implemented": true, - "kind": "constructor", - "modifiers": [ - { - "arguments": [ - { - "argumentTypes": null, - "id": 22, - "name": "tokenAddress", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 15, - "src": "408:12:0", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - { - "argumentTypes": null, - "id": 23, - "name": "blockFreezePeriodSeconds", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 17, - "src": "422:24:0", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - { - "argumentTypes": null, - "id": 24, - "name": "adminFeeFraction", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 19, - "src": "448:16:0", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - } - ], - "id": 25, - "modifierName": { - "argumentTypes": null, - "id": 21, - "name": "Monoplasma", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 871, - "src": "397:10:0", - "typeDescriptions": { - "typeIdentifier": "t_type$_t_contract$_Monoplasma_$871_$", - "typeString": "type(contract Monoplasma)" - } - }, - "nodeType": "ModifierInvocation", - "src": "397:68:0" - } - ], - "name": "", - "nodeType": "FunctionDefinition", - "parameters": { - "id": 20, - "nodeType": "ParameterList", - "parameters": [ - { - "constant": false, - "id": 11, - "name": "operator", - "nodeType": "VariableDeclaration", - "scope": 36, - "src": "267:16:0", - "stateVariable": false, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - }, - "typeName": { - "id": 10, - "name": "address", - "nodeType": "ElementaryTypeName", - "src": "267:7:0", - "stateMutability": "nonpayable", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - "value": null, - "visibility": "internal" - }, - { - "constant": false, - "id": 13, - "name": "joinPartStreamId", - "nodeType": "VariableDeclaration", - "scope": 36, - "src": "285:30:0", - "stateVariable": false, - "storageLocation": "memory", - "typeDescriptions": { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string" - }, - "typeName": { - "id": 12, - "name": "string", - "nodeType": "ElementaryTypeName", - "src": "285:6:0", - "typeDescriptions": { - "typeIdentifier": "t_string_storage_ptr", - "typeString": "string" - } - }, - "value": null, - "visibility": "internal" - }, - { - "constant": false, - "id": 15, - "name": "tokenAddress", - "nodeType": "VariableDeclaration", - "scope": 36, - "src": "317:20:0", - "stateVariable": false, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - }, - "typeName": { - "id": 14, - "name": "address", - "nodeType": "ElementaryTypeName", - "src": "317:7:0", - "stateMutability": "nonpayable", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - "value": null, - "visibility": "internal" - }, - { - "constant": false, - "id": 17, - "name": "blockFreezePeriodSeconds", - "nodeType": "VariableDeclaration", - "scope": 36, - "src": "339:29:0", - "stateVariable": false, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - }, - "typeName": { - "id": 16, - "name": "uint", - "nodeType": "ElementaryTypeName", - "src": "339:4:0", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - "value": null, - "visibility": "internal" - }, - { - "constant": false, - "id": 19, - "name": "adminFeeFraction", - "nodeType": "VariableDeclaration", - "scope": 36, - "src": "370:21:0", - "stateVariable": false, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - }, - "typeName": { - "id": 18, - "name": "uint", - "nodeType": "ElementaryTypeName", - "src": "370:4:0", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - "value": null, - "visibility": "internal" - } - ], - "src": "266:126:0" - }, - "returnParameters": { - "id": 26, - "nodeType": "ParameterList", - "parameters": [], - "src": "473:0:0" - }, - "scope": 37, - "src": "255:299:0", - "stateMutability": "nonpayable", - "superFunction": null, - "visibility": "public" - } - ], - "scope": 38, - "src": "73:483:0" - } - ], - "src": "0:557:0" - }, - "legacyAST": { - "absolutePath": "/home/heynow/streamr/data-union-server/contracts/DataunionVault.sol", - "exportedSymbols": { - "DataunionVault": [ - 37 - ] - }, - "id": 38, - "nodeType": "SourceUnit", - "nodes": [ - { - "id": 1, - "literals": [ - "solidity", - "^", - "0.5", - ".16" - ], - "nodeType": "PragmaDirective", - "src": "0:24:0" - }, - { - "absolutePath": "monoplasma/contracts/Monoplasma.sol", - "file": "monoplasma/contracts/Monoplasma.sol", - "id": 2, - "nodeType": "ImportDirective", - "scope": 38, - "sourceUnit": 872, - "src": "26:45:0", - "symbolAliases": [], - "unitAlias": "" - }, - { - "baseContracts": [ - { - "arguments": null, - "baseName": { - "contractScope": null, - "id": 3, - "name": "Monoplasma", - "nodeType": "UserDefinedTypeName", - "referencedDeclaration": 871, - "src": "100:10:0", - "typeDescriptions": { - "typeIdentifier": "t_contract$_Monoplasma_$871", - "typeString": "contract Monoplasma" - } - }, - "id": 4, - "nodeType": "InheritanceSpecifier", - "src": "100:10:0" - } - ], - "contractDependencies": [ - 276, - 871, - 944 - ], - "contractKind": "contract", - "documentation": null, - "fullyImplemented": true, - "id": 37, - "linearizedBaseContracts": [ - 37, - 871, - 944, - 276 - ], - "name": "DataunionVault", - "nodeType": "ContractDefinition", - "nodes": [ - { - "constant": false, - "id": 6, - "name": "joinPartStream", - "nodeType": "VariableDeclaration", - "scope": 37, - "src": "118:28:0", - "stateVariable": true, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_string_storage", - "typeString": "string" - }, - "typeName": { - "id": 5, - "name": "string", - "nodeType": "ElementaryTypeName", - "src": "118:6:0", - "typeDescriptions": { - "typeIdentifier": "t_string_storage_ptr", - "typeString": "string" - } - }, - "value": null, - "visibility": "public" - }, - { - "constant": false, - "id": 9, - "name": "version", - "nodeType": "VariableDeclaration", - "scope": 37, - "src": "225:23:0", - "stateVariable": true, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - }, - "typeName": { - "id": 7, - "name": "uint", - "nodeType": "ElementaryTypeName", - "src": "225:4:0", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - "value": { - "argumentTypes": null, - "hexValue": "31", - "id": 8, - "isConstant": false, - "isLValue": false, - "isPure": true, - "kind": "number", - "lValueRequested": false, - "nodeType": "Literal", - "src": "247:1:0", - "subdenomination": null, - "typeDescriptions": { - "typeIdentifier": "t_rational_1_by_1", - "typeString": "int_const 1" - }, - "value": "1" - }, - "visibility": "public" - }, - { - "body": { - "id": 35, - "nodeType": "Block", - "src": "473:81:0", - "statements": [ - { - "expression": { - "argumentTypes": null, - "arguments": [ - { - "argumentTypes": null, - "id": 28, - "name": "operator", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 11, - "src": "495:8:0", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - } - ], - "expression": { - "argumentTypes": [ - { - "typeIdentifier": "t_address", - "typeString": "address" - } - ], - "id": 27, - "name": "setOperator", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 366, - "src": "483:11:0", - "typeDescriptions": { - "typeIdentifier": "t_function_internal_nonpayable$_t_address_$returns$__$", - "typeString": "function (address)" - } - }, - "id": 29, - "isConstant": false, - "isLValue": false, - "isPure": false, - "kind": "functionCall", - "lValueRequested": false, - "names": [], - "nodeType": "FunctionCall", - "src": "483:21:0", - "typeDescriptions": { - "typeIdentifier": "t_tuple$__$", - "typeString": "tuple()" - } - }, - "id": 30, - "nodeType": "ExpressionStatement", - "src": "483:21:0" - }, - { - "expression": { - "argumentTypes": null, - "id": 33, - "isConstant": false, - "isLValue": false, - "isPure": false, - "lValueRequested": false, - "leftHandSide": { - "argumentTypes": null, - "id": 31, - "name": "joinPartStream", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 6, - "src": "514:14:0", - "typeDescriptions": { - "typeIdentifier": "t_string_storage", - "typeString": "string storage ref" - } - }, - "nodeType": "Assignment", - "operator": "=", - "rightHandSide": { - "argumentTypes": null, - "id": 32, - "name": "joinPartStreamId", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 13, - "src": "531:16:0", - "typeDescriptions": { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string memory" - } - }, - "src": "514:33:0", - "typeDescriptions": { - "typeIdentifier": "t_string_storage", - "typeString": "string storage ref" - } - }, - "id": 34, - "nodeType": "ExpressionStatement", - "src": "514:33:0" - } - ] - }, - "documentation": null, - "id": 36, - "implemented": true, - "kind": "constructor", - "modifiers": [ - { - "arguments": [ - { - "argumentTypes": null, - "id": 22, - "name": "tokenAddress", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 15, - "src": "408:12:0", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - { - "argumentTypes": null, - "id": 23, - "name": "blockFreezePeriodSeconds", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 17, - "src": "422:24:0", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - { - "argumentTypes": null, - "id": 24, - "name": "adminFeeFraction", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 19, - "src": "448:16:0", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - } - ], - "id": 25, - "modifierName": { - "argumentTypes": null, - "id": 21, - "name": "Monoplasma", - "nodeType": "Identifier", - "overloadedDeclarations": [], - "referencedDeclaration": 871, - "src": "397:10:0", - "typeDescriptions": { - "typeIdentifier": "t_type$_t_contract$_Monoplasma_$871_$", - "typeString": "type(contract Monoplasma)" - } - }, - "nodeType": "ModifierInvocation", - "src": "397:68:0" - } - ], - "name": "", - "nodeType": "FunctionDefinition", - "parameters": { - "id": 20, - "nodeType": "ParameterList", - "parameters": [ - { - "constant": false, - "id": 11, - "name": "operator", - "nodeType": "VariableDeclaration", - "scope": 36, - "src": "267:16:0", - "stateVariable": false, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - }, - "typeName": { - "id": 10, - "name": "address", - "nodeType": "ElementaryTypeName", - "src": "267:7:0", - "stateMutability": "nonpayable", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - "value": null, - "visibility": "internal" - }, - { - "constant": false, - "id": 13, - "name": "joinPartStreamId", - "nodeType": "VariableDeclaration", - "scope": 36, - "src": "285:30:0", - "stateVariable": false, - "storageLocation": "memory", - "typeDescriptions": { - "typeIdentifier": "t_string_memory_ptr", - "typeString": "string" - }, - "typeName": { - "id": 12, - "name": "string", - "nodeType": "ElementaryTypeName", - "src": "285:6:0", - "typeDescriptions": { - "typeIdentifier": "t_string_storage_ptr", - "typeString": "string" - } - }, - "value": null, - "visibility": "internal" - }, - { - "constant": false, - "id": 15, - "name": "tokenAddress", - "nodeType": "VariableDeclaration", - "scope": 36, - "src": "317:20:0", - "stateVariable": false, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - }, - "typeName": { - "id": 14, - "name": "address", - "nodeType": "ElementaryTypeName", - "src": "317:7:0", - "stateMutability": "nonpayable", - "typeDescriptions": { - "typeIdentifier": "t_address", - "typeString": "address" - } - }, - "value": null, - "visibility": "internal" - }, - { - "constant": false, - "id": 17, - "name": "blockFreezePeriodSeconds", - "nodeType": "VariableDeclaration", - "scope": 36, - "src": "339:29:0", - "stateVariable": false, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - }, - "typeName": { - "id": 16, - "name": "uint", - "nodeType": "ElementaryTypeName", - "src": "339:4:0", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - "value": null, - "visibility": "internal" - }, - { - "constant": false, - "id": 19, - "name": "adminFeeFraction", - "nodeType": "VariableDeclaration", - "scope": 36, - "src": "370:21:0", - "stateVariable": false, - "storageLocation": "default", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - }, - "typeName": { - "id": 18, - "name": "uint", - "nodeType": "ElementaryTypeName", - "src": "370:4:0", - "typeDescriptions": { - "typeIdentifier": "t_uint256", - "typeString": "uint256" - } - }, - "value": null, - "visibility": "internal" - } - ], - "src": "266:126:0" - }, - "returnParameters": { - "id": 26, - "nodeType": "ParameterList", - "parameters": [], - "src": "473:0:0" - }, - "scope": 37, - "src": "255:299:0", - "stateMutability": "nonpayable", - "superFunction": null, - "visibility": "public" - } - ], - "scope": 38, - "src": "73:483:0" - } - ], - "src": "0:557:0" - }, - "compiler": { - "name": "solc", - "version": "0.5.16+commit.9c3226ce.Emscripten.clang", - "optimizer": false, - "runs": 200 - }, - "networks": {}, - "schemaVersion": "2.2.4", - "updatedAt": "2020-05-14T11:43:55.112Z" -} \ No newline at end of file diff --git a/contracts/DataUnionFactoryMainnet.json b/contracts/DataUnionFactoryMainnet.json new file mode 100644 index 000000000..53de97168 --- /dev/null +++ b/contracts/DataUnionFactoryMainnet.json @@ -0,0 +1,6163 @@ +{ + "contractName": "DataUnionFactoryMainnet", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_token_mediator", + "type": "address" + }, + { + "internalType": "address", + "name": "_data_union_mainnet_template", + "type": "address" + }, + { + "internalType": "address", + "name": "_data_union_sidechain_template", + "type": "address" + }, + { + "internalType": "address", + "name": "_data_union_sidechain_factory", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_sidechain_maxgas", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "mainnet", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sidechain", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "template", + "type": "address" + } + ], + "name": "MainnetDUCreated", + "type": "event" + }, + { + "inputs": [], + "name": "amb", + "outputs": [ + { + "internalType": "contract IAMB", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "data_union_mainnet_template", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "data_union_sidechain_factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "data_union_sidechain_template", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sidechain_maxgas", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token_mediator", + "outputs": [ + { + "internalType": "contract ITokenMediator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "mainet_address", + "type": "address" + } + ], + "name": "sidechainAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "deployer", + "type": "address" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "mainnetAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "adminFeeFraction", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "agents", + "type": "address[]" + }, + { + "internalType": "string", + "name": "name", + "type": "string" + } + ], + "name": "deployNewDataUnion", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "metadata": "{\"compiler\":{\"version\":\"0.6.6+commit.6c089d02\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_token_mediator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_data_union_mainnet_template\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_data_union_sidechain_template\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_data_union_sidechain_factory\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_sidechain_maxgas\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"mainnet\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sidechain\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"template\",\"type\":\"address\"}],\"name\":\"MainnetDUCreated\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"amb\",\"outputs\":[{\"internalType\":\"contract IAMB\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"data_union_mainnet_template\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"data_union_sidechain_factory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"data_union_sidechain_template\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"adminFeeFraction\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"agents\",\"type\":\"address[]\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"name\":\"deployNewDataUnion\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"deployer\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"name\",\"type\":\"string\"}],\"name\":\"mainnetAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"mainet_address\",\"type\":\"address\"}],\"name\":\"sidechainAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sidechain_maxgas\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token_mediator\",\"outputs\":[{\"internalType\":\"contract ITokenMediator\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"}],\"devdoc\":{\"methods\":{}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"/home/heynow/streamr/data-union-solidity/contracts/DataUnionFactoryMainnet.sol\":\"DataUnionFactoryMainnet\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/heynow/streamr/data-union-solidity/contracts/CloneLib.sol\":{\"keccak256\":\"0xf9082fe41a9e05b37cf87f0d1d67ca16d32c4100a2a27a6fc9d8f3f8364a7486\",\"urls\":[\"bzz-raw://b928f45b1904c399db9508c093604531bd215afeb4686827ab46daa7aee1938e\",\"dweb:/ipfs/QmdnBUvpCDc93fHZe7o6bitGRZo4PFTx9NprgziqHsVHFw\"]},\"/home/heynow/streamr/data-union-solidity/contracts/DataUnionFactoryMainnet.sol\":{\"keccak256\":\"0x6bd5ad2171e1b91d5bfe345192db0cc02abc6e2c753745a0abc5ed8fa04e6637\",\"urls\":[\"bzz-raw://003edd72510ebfede31c3f498338337c068a8ab0dd604f452798236199306dd4\",\"dweb:/ipfs/QmWL4QHskrmwvEM3auxSfjKG64WTXhuevayjhMwq3S8T9f\"]},\"/home/heynow/streamr/data-union-solidity/contracts/IAMB.sol\":{\"keccak256\":\"0x63b46d3087a8ba558cb36d99ffe5b2f0241446adc4c8df4219ce86f97727e168\",\"urls\":[\"bzz-raw://66d9c8c817e90b25f28b3d79a69b440018b49633a7bbaf6fe4d1788b2ff5e6eb\",\"dweb:/ipfs/QmSdrqpaZ3R3NGFuF6jnbo8cCgV5ghdW5iWrnV59qavYP2\"]},\"/home/heynow/streamr/data-union-solidity/contracts/ITokenMediator.sol\":{\"keccak256\":\"0x1f21e943f4e125dd1c4353db29880b63f8fa7a299ba862432c8a4e14807873e2\",\"urls\":[\"bzz-raw://4ddc233243efce6c2582b63e3be28c4f90f32d60b5d4b6fe8331106d970a8435\",\"dweb:/ipfs/QmQzLwvxU3ixfLbdpuYdpqvW61xjPbnXDTaaWNjRJUhvSK\"]},\"openzeppelin-solidity/contracts/math/SafeMath.sol\":{\"keccak256\":\"0x9a9cf02622cd7a64261b10534fc3260449da25c98c9e96d1b4ae8110a20e5806\",\"urls\":[\"bzz-raw://2df142592d1dc267d9549049ee3317fa190d2f87eaa565f86ab05ec83f7ab8f5\",\"dweb:/ipfs/QmSkJtcfWo7c42KnL5hho6GFxK6HRNV91XABx1P7xDtfLV\"]},\"openzeppelin-solidity/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0x5c26b39d26f7ed489e555d955dcd3e01872972e71fdd1528e93ec164e4f23385\",\"urls\":[\"bzz-raw://efdc632af6960cf865dbc113665ea1f5b90eab75cc40ec062b2f6ae6da582017\",\"dweb:/ipfs/QmfAZFDuG62vxmAN9DnXApv7e7PMzPqi4RkqqZHLMSQiY5\"]}},\"version\":1}", + "bytecode": "0x608060405234801561001057600080fd5b50604051610aab380380610aab833981810160405260c081101561003357600080fd5b5080516020808301516040808501516060860151608087015160a090970151600680546001600160a01b03199081166001600160a01b03808b1691909117909255600580548216838916179081905560008054831684881617905560018054831684871617905560028054909216838c1617909155855163cd59658360e01b81529551989996989497939695929491169263cd5965839260048181019391829003018186803b1580156100e557600080fd5b505afa1580156100f9573d6000803e3d6000fd5b505050506040513d602081101561010f57600080fd5b5051600480546001600160a01b039092166001600160a01b03199092169190911790556003555050505050610962806101496000396000f3fe608060405234801561001057600080fd5b506004361061009e5760003560e01c8063c5a8c91f11610066578063c5a8c91f14610117578063cb8a191b1461011f578063d4c31bd41461025e578063e4a154a414610314578063fc0c546a1461031c5761009e565b8063015a0da0146100a35780631062b39a146100c757806317c2a98c146100cf57806335813bc1146100f5578063692199d4146100fd575b600080fd5b6100ab610324565b604080516001600160a01b039092168252519081900360200190f35b6100ab610333565b6100ab600480360360208110156100e557600080fd5b50356001600160a01b0316610342565b6100ab61036c565b61010561037b565b60408051918252519081900360200190f35b6100ab610381565b6100ab6004803603608081101561013557600080fd5b6001600160a01b038235169160208101359181019060608101604082013564010000000081111561016557600080fd5b82018360208201111561017757600080fd5b8035906020019184602083028401116401000000008311171561019957600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092959493602081019350359150506401000000008111156101e957600080fd5b8201836020820111156101fb57600080fd5b8035906020019184600183028401116401000000008311171561021d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610390945050505050565b6100ab6004803603604081101561027457600080fd5b6001600160a01b03823516919081019060408101602082013564010000000081111561029f57600080fd5b8201836020820111156102b157600080fd5b803590602001918460018302840111640100000000831117156102d357600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061062d945050505050565b6100ab6106ee565b6100ab6106fd565b6000546001600160a01b031681565b6004546001600160a01b031681565b600154600254600091610366916001600160a01b039182169190811690851661070c565b92915050565b6002546001600160a01b031681565b60035481565b6005546001600160a01b031681565b60008082336040516020018080602001836001600160a01b03166001600160a01b03168152602001828103825284818151815260200191508051906020019080838360005b838110156103ed5781810151838201526020016103d5565b50505050905090810190601f16801561041a5780820380516001836020036101000a031916815260200191505b5093505050506040516020818303038152906040528051906020012090506060600660009054906101000a90046001600160a01b0316600560009054906101000a90046001600160a01b0316600260009054906101000a90046001600160a01b0316600354600160009054906101000a90046001600160a01b03168b8b8b60405160240180896001600160a01b03166001600160a01b03168152602001886001600160a01b03166001600160a01b03168152602001876001600160a01b03166001600160a01b03168152602001868152602001856001600160a01b03166001600160a01b03168152602001846001600160a01b03166001600160a01b0316815260200183815260200180602001828103825283818151815260200191508051906020019060200280838360005b8381101561055f578181015183820152602001610547565b50506040805193909501838103601f190184529094525060208101805163fb6470c960e01b6001600160e01b0390911617905260008054919e509c506105c59b506105be9a506001600160a01b03169850610778975050505050505050565b83856107ca565b9050876001600160a01b03166105da82610342565b600054604080516001600160a01b039283168152905192821692918516917f7bb36c64b37ae129eda8a24fd78defec04cc7a06bb27863c5a4571dd5d70acee9181900360200190a4979650505050505050565b604080516001600160a01b0384168183015260208082019283528351606083015283516000938493869388938392608001918601908083838a5b8381101561067f578181015183820152602001610667565b50505050905090810190601f1680156106ac5780820380516001836020036101000a031916815260200191505b5060408051601f1981840301815291905280516020909101206000549095506106e694506001600160a01b0316925030915084905061070c565b949350505050565b6001546001600160a01b031681565b6006546001600160a01b031681565b60008061071885610778565b8051602091820120604080516001600160f81b0319818501526bffffffffffffffffffffffff19606089901b1660218201526035810187905260558082019390935281518082039093018352607501905280519101209150509392505050565b604080516057810190915260378152733d602d80600a3d3981f3363d3d373d3d3d363d7360601b602082015260609190911b60348201526e5af43d82803e903d91602b57fd5bf360881b604882015290565b825160009082816020870184f591506001600160a01b03821661082b576040805162461bcd60e51b8152602060048201526014602482015273195c9c9bdc97d85b1c9958591e50dc99585d195960621b604482015290519081900360640190fd5b835115610924576000826001600160a01b0316856040518082805190602001908083835b6020831061086e5780518252601f19909201916020918201910161084f565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146108d0576040519150601f19603f3d011682016040523d82523d6000602084013e6108d5565b606091505b5050905080610922576040805162461bcd60e51b815260206004820152601460248201527332b93937b92fb4b734ba34b0b634bd30ba34b7b760611b604482015290519081900360640190fd5b505b50939250505056fea264697066735822122084945f9903b2d49dc74f1c2fb52a86a2b798a1ca3d19412037d748182369d7ea64736f6c63430006060033", + "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061009e5760003560e01c8063c5a8c91f11610066578063c5a8c91f14610117578063cb8a191b1461011f578063d4c31bd41461025e578063e4a154a414610314578063fc0c546a1461031c5761009e565b8063015a0da0146100a35780631062b39a146100c757806317c2a98c146100cf57806335813bc1146100f5578063692199d4146100fd575b600080fd5b6100ab610324565b604080516001600160a01b039092168252519081900360200190f35b6100ab610333565b6100ab600480360360208110156100e557600080fd5b50356001600160a01b0316610342565b6100ab61036c565b61010561037b565b60408051918252519081900360200190f35b6100ab610381565b6100ab6004803603608081101561013557600080fd5b6001600160a01b038235169160208101359181019060608101604082013564010000000081111561016557600080fd5b82018360208201111561017757600080fd5b8035906020019184602083028401116401000000008311171561019957600080fd5b91908080602002602001604051908101604052809392919081815260200183836020028082843760009201919091525092959493602081019350359150506401000000008111156101e957600080fd5b8201836020820111156101fb57600080fd5b8035906020019184600183028401116401000000008311171561021d57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550610390945050505050565b6100ab6004803603604081101561027457600080fd5b6001600160a01b03823516919081019060408101602082013564010000000081111561029f57600080fd5b8201836020820111156102b157600080fd5b803590602001918460018302840111640100000000831117156102d357600080fd5b91908080601f01602080910402602001604051908101604052809392919081815260200183838082843760009201919091525092955061062d945050505050565b6100ab6106ee565b6100ab6106fd565b6000546001600160a01b031681565b6004546001600160a01b031681565b600154600254600091610366916001600160a01b039182169190811690851661070c565b92915050565b6002546001600160a01b031681565b60035481565b6005546001600160a01b031681565b60008082336040516020018080602001836001600160a01b03166001600160a01b03168152602001828103825284818151815260200191508051906020019080838360005b838110156103ed5781810151838201526020016103d5565b50505050905090810190601f16801561041a5780820380516001836020036101000a031916815260200191505b5093505050506040516020818303038152906040528051906020012090506060600660009054906101000a90046001600160a01b0316600560009054906101000a90046001600160a01b0316600260009054906101000a90046001600160a01b0316600354600160009054906101000a90046001600160a01b03168b8b8b60405160240180896001600160a01b03166001600160a01b03168152602001886001600160a01b03166001600160a01b03168152602001876001600160a01b03166001600160a01b03168152602001868152602001856001600160a01b03166001600160a01b03168152602001846001600160a01b03166001600160a01b0316815260200183815260200180602001828103825283818151815260200191508051906020019060200280838360005b8381101561055f578181015183820152602001610547565b50506040805193909501838103601f190184529094525060208101805163fb6470c960e01b6001600160e01b0390911617905260008054919e509c506105c59b506105be9a506001600160a01b03169850610778975050505050505050565b83856107ca565b9050876001600160a01b03166105da82610342565b600054604080516001600160a01b039283168152905192821692918516917f7bb36c64b37ae129eda8a24fd78defec04cc7a06bb27863c5a4571dd5d70acee9181900360200190a4979650505050505050565b604080516001600160a01b0384168183015260208082019283528351606083015283516000938493869388938392608001918601908083838a5b8381101561067f578181015183820152602001610667565b50505050905090810190601f1680156106ac5780820380516001836020036101000a031916815260200191505b5060408051601f1981840301815291905280516020909101206000549095506106e694506001600160a01b0316925030915084905061070c565b949350505050565b6001546001600160a01b031681565b6006546001600160a01b031681565b60008061071885610778565b8051602091820120604080516001600160f81b0319818501526bffffffffffffffffffffffff19606089901b1660218201526035810187905260558082019390935281518082039093018352607501905280519101209150509392505050565b604080516057810190915260378152733d602d80600a3d3981f3363d3d373d3d3d363d7360601b602082015260609190911b60348201526e5af43d82803e903d91602b57fd5bf360881b604882015290565b825160009082816020870184f591506001600160a01b03821661082b576040805162461bcd60e51b8152602060048201526014602482015273195c9c9bdc97d85b1c9958591e50dc99585d195960621b604482015290519081900360640190fd5b835115610924576000826001600160a01b0316856040518082805190602001908083835b6020831061086e5780518252601f19909201916020918201910161084f565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d80600081146108d0576040519150601f19603f3d011682016040523d82523d6000602084013e6108d5565b606091505b5050905080610922576040805162461bcd60e51b815260206004820152601460248201527332b93937b92fb4b734ba34b0b634bd30ba34b7b760611b604482015290519081900360640190fd5b505b50939250505056fea264697066735822122084945f9903b2d49dc74f1c2fb52a86a2b798a1ca3d19412037d748182369d7ea64736f6c63430006060033", + "immutableReferences": {}, + "sourceMap": "332:3065:1:-:0;;;825:694;5:9:-1;2:2;;;27:1;24;17:12;2:2;825:694:1;;;;;;;;;;;;;;;15:3:-1;10;7:12;4:2;;;32:1;29;22:12;4:2;-1:-1;825:694:1;;;;;;;;;;;;;;;;;;;;;;;;;1131:5;:14;;-1:-1:-1;;;;;;1131:14:1;;;-1:-1:-1;;;;;1131:14:1;;;;;;;;;;1155;:48;;;;;;;;;;;;-1:-1:-1;1213:58:1;;;;;;;;;;-1:-1:-1;1281:62:1;;;;;;;;;;1353:28;:60;;;;;;;;;;;;1434:31;;-1:-1:-1;;;1434:31:1;;;;825:694;;;;;;;;;;;1434:14;;;:29;;-1:-1:-1;1434:31:1;;;;;;;;;;:14;:31;;;2:2:-1;;;;27:1;24;17:12;2:2;1434:31:1;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;1434:31:1;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;1434:31:1;1423:3;:43;;-1:-1:-1;;;;;1423:43:1;;;-1:-1:-1;;;;;;1423:43:1;;;;;;;;;1476:16;:36;-1:-1:-1;;;;;332:3065:1;;;;;;", + "deployedSourceMap": "332:3065:1:-:0;;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;332:3065:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12:1:-1;9;2:12;493:42:1;;;:::i;:::-;;;;-1:-1:-1;;;;;493:42:1;;;;;;;;;;;;;;735:15;;;:::i;1526:300::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;1526:300:1;-1:-1:-1;;;;;1526:300:1;;:::i;649:43::-;;;:::i;698:31::-;;;:::i;:::-;;;;;;;;;;;;;;;;756:36;;;:::i;2546:849::-;;;;;;15:3:-1;10;7:12;4:2;;;32:1;29;22:12;4:2;-1:-1;;;;;2546:849:1;;;;;;;;;;;;;;;;;;;27:11:-1;11:28;;8:2;;;52:1;49;42:12;8:2;2546:849:1;;41:9:-1;34:4;18:14;14:25;11:40;8:2;;;64:1;61;54:12;8:2;2546:849:1;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;39:11;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;2546:849:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;2546:849:1;;;;;;;;-1:-1:-1;2546:849:1;;-1:-1:-1;;27:11;11:28;;8:2;;;52:1;49;42:12;8:2;2546:849:1;;41:9:-1;34:4;18:14;14:25;11:40;8:2;;;64:1;61;54:12;8:2;2546:849:1;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;39:11;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;2546:849:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;2546:849:1;;-1:-1:-1;2546:849:1;;-1:-1:-1;;;;;2546:849:1:i;1846:336::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;;;;;1846:336:1;;;;;;;;;;;;;;;27:11:-1;11:28;;8:2;;;52:1;49;42:12;8:2;1846:336:1;;41:9:-1;34:4;18:14;14:25;11:40;8:2;;;64:1;61;54:12;8:2;1846:336:1;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;39:11;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;1846:336:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;1846:336:1;;-1:-1:-1;1846:336:1;;-1:-1:-1;;;;;1846:336:1:i;599:44::-;;;:::i;798:20::-;;;:::i;493:42::-;;;-1:-1:-1;;;;;493:42:1;;:::o;735:15::-;;;-1:-1:-1;;;;;735:15:1;;:::o;1526:300::-;1692:29;;1735:28;;1613:7;;1643:176;;-1:-1:-1;;;;;1692:29:1;;;;1735:28;;;;1785:23;;1643:35;:176::i;:::-;1636:183;1526:300;-1:-1:-1;;1526:300:1:o;649:43::-;;;-1:-1:-1;;;;;649:43:1;;:::o;698:31::-;;;;:::o;756:36::-;;;-1:-1:-1;;;;;756:36:1;;:::o;2546:849::-;2692:7;2715:12;2757:4;2764:10;2740:35;;;;;;;;;-1:-1:-1;;;;;2740:35:1;-1:-1:-1;;;;;2740:35:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;2740:35:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;49:4:-1;39:7;30;26:21;22:32;13:7;6:49;2740:35:1;;;2730:46;;;;;;2715:61;;2786:17;2923:5;;;;;;;;;-1:-1:-1;;;;;2923:5:1;2942:14;;;;;;;;;-1:-1:-1;;;;;2942:14:1;2970:28;;;;;;;;;-1:-1:-1;;;;;2970:28:1;3012:16;;3042:29;;;;;;;;;-1:-1:-1;;;;;3042:29:1;3085:5;3104:16;3134:6;2806:344;;;;;;-1:-1:-1;;;;;2806:344:1;-1:-1:-1;;;;;2806:344:1;;;;;;-1:-1:-1;;;;;2806:344:1;-1:-1:-1;;;;;2806:344:1;;;;;;-1:-1:-1;;;;;2806:344:1;-1:-1:-1;;;;;2806:344:1;;;;;;;;;;;-1:-1:-1;;;;;2806:344:1;-1:-1:-1;;;;;2806:344:1;;;;;;-1:-1:-1;;;;;2806:344:1;-1:-1:-1;;;;;2806:344:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;-1:-1;;2806:344:1;;;;;;;26:21:-1;;;-1:-1;;22:32;6:49;;2806:344:1;;;-1:-1:-1;49:4;25:18;;61:17;;-1:-1;;;;;;;;182:15;;;179:29;160:49;;-1:-1;3235:27:1;;2806:344;;-1:-1:-1;;;3173:103:1;;-1:-1:-1;3212:51:1;;-1:-1:-1;;;;;;3235:27:1;;-1:-1:-1;3212:22:1;;-1:-1:-1;;;;;;;;3212:51:1:i;:::-;3265:4;3271;3173:38;:103::i;:::-;3160:116;;3334:5;-1:-1:-1;;;;;3291:78:1;3312:20;3329:2;3312:16;:20::i;:::-;3341:27;;3291:78;;;-1:-1:-1;;;;;3341:27:1;;;3291:78;;;;;;;;;;;;;;;;;;;;;3386:2;2546:849;-1:-1:-1;;;;;;;2546:849:1:o;1846:336::-;1993:33;;;-1:-1:-1;;;;;1993:33:1;;;;;;;;;;;;;;;;;;;;;1945:7;;;;2010:4;;2017:8;;1993:33;;;;;;;;;;;1945:7;8:100:-1;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;1993:33:1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;1993:33:1;;;-1:-1:-1;;26:21;;;22:32;6:49;;1993:33:1;;;1983:44;;49:4:-1;1983:44:1;;;;2093:27;;1983:44;;-1:-1:-1;2044:131:1;;-1:-1:-1;;;;;;2093:27:1;;-1:-1:-1;2142:4:1;;-1:-1:-1;1983:44:1;;-1:-1:-1;2044:35:1;:131::i;:::-;2037:138;1846:336;-1:-1:-1;;;;1846:336:1:o;599:44::-;;;-1:-1:-1;;;;;599:44:1;;:::o;798:20::-;;;-1:-1:-1;;;;;798:20:1;;:::o;1371:393:0:-;1510:13;1535:16;1564:23;1578:8;1564:13;:23::i;:::-;1554:34;;;;;;;1639:114;;;-1:-1:-1;;;;;;1639:114:0;;;;-1:-1:-1;;1639:114:0;;;;;;;;;;;;;;;;;;;;;;;;;26:21:-1;;;22:32;;;6:49;;1639:114:0;;;;1629:125;;;;;;-1:-1:-1;;1371:393:0;;;;;:::o;504:712::-;683:4;677:11;;724:4;714:15;;701:29;;;840:4;827:18;;-1:-1:-1;;;973:4:0;963:15;;956:91;568:17;619;;;;1077:4;1067:15;;1060:36;-1:-1:-1;;;1126:4:0;1116:15;;1109:91;677:11;655:555::o;2256:512::-;2446:11;;2399:21;;2532:4;2446:11;2520:4;2510:15;;2399:21;2499:38;2490:47;-1:-1:-1;;;;;;2564:19:0;;2556:52;;;;;-1:-1:-1;;;2556:52:0;;;;;;;;;;;;-1:-1:-1;;;2556:52:0;;;;;;;;;;;;;;;2622:15;;:20;2618:144;;2659:12;2677:5;-1:-1:-1;;;;;2677:10:0;2688:8;2677:20;;;;;;;;;;;;;36:153:-1;66:2;61:3;58:11;36:153;;176:10;;164:23;;-1:-1;;139:12;;;;98:2;89:12;;;;114;36:153;;;274:1;267:3;263:2;259:12;254:3;250:22;246:30;315:4;311:9;305:3;299:10;295:26;356:4;350:3;344:10;340:21;389:7;380;377:20;372:3;365:33;3:399;;;2677:20:0;;;;;;;;;;;;;;;;;;;;;;;;12:1:-1;19;14:27;;;;67:4;61:11;56:16;;134:4;130:9;123:4;105:16;101:27;97:43;94:1;90:51;84:4;77:65;157:16;154:1;147:27;211:16;208:1;201:4;198:1;194:12;179:49;5:228;;14:27;32:4;27:9;;5:228;;2658:39:0;;;2719:7;2711:40;;;;;-1:-1:-1;;;2711:40:0;;;;;;;;;;;;-1:-1:-1;;;2711:40:0;;;;;;;;;;;;;;;2618:144;;2256:512;;;;;;:::o", + "source": "pragma solidity 0.6.6;\n\nimport \"openzeppelin-solidity/contracts/token/ERC20/IERC20.sol\";\nimport \"openzeppelin-solidity/contracts/math/SafeMath.sol\";\nimport \"./CloneLib.sol\";\nimport \"./IAMB.sol\";\nimport \"./ITokenMediator.sol\";\n\ninterface IDataUnionMainnet {\n function sidechainAddress() external view returns (address proxy);\n}\n\n\ncontract DataUnionFactoryMainnet {\n event MainnetDUCreated(address indexed mainnet, address indexed sidechain, address indexed owner, address template);\n\n address public data_union_mainnet_template;\n\n // needed to calculate address of sidechain contract\n address public data_union_sidechain_template;\n address public data_union_sidechain_factory;\n uint256 public sidechain_maxgas;\n IAMB public amb;\n ITokenMediator public token_mediator;\n address public token;\n\n constructor(address _token,\n address _token_mediator,\n address _data_union_mainnet_template,\n address _data_union_sidechain_template,\n address _data_union_sidechain_factory,\n uint256 _sidechain_maxgas)\n public\n {\n token = _token;\n token_mediator = ITokenMediator(_token_mediator);\n data_union_mainnet_template = _data_union_mainnet_template;\n data_union_sidechain_template = _data_union_sidechain_template;\n data_union_sidechain_factory = _data_union_sidechain_factory;\n amb = IAMB(token_mediator.bridgeContract());\n sidechain_maxgas = _sidechain_maxgas;\n }\n\n\n function sidechainAddress(address mainet_address)\n public view\n returns (address)\n {\n return CloneLib.predictCloneAddressCreate2(\n data_union_sidechain_template,\n data_union_sidechain_factory,\n bytes32(uint256(mainet_address))\n );\n }\n /*\n\n */\n function mainnetAddress(address deployer, string memory name)\n public view\n returns (address)\n {\n bytes32 salt = keccak256(abi.encode(bytes(name), deployer));\n return CloneLib.predictCloneAddressCreate2(\n data_union_mainnet_template,\n address(this),\n salt\n );\n }\n\n\n/*\n function initialize(\n address _token,\n address _token_mediator,\n address _sidechain_DU_factory,\n uint256 _sidechain_maxgas,\n address _sidechain_template_DU,\n address _owner,\n uint256 adminFeeFraction,\n address[] memory agents\n ) public {\n users can only deploy with salt = their key.\n*/\n function deployNewDataUnion(address owner, uint256 adminFeeFraction, address[] memory agents, string memory name)\n public\n returns (address)\n {\n bytes32 salt = keccak256(abi.encode(bytes(name), msg.sender));\n bytes memory data = abi.encodeWithSignature(\"initialize(address,address,address,uint256,address,address,uint256,address[])\",\n token,\n token_mediator,\n data_union_sidechain_factory,\n sidechain_maxgas,\n data_union_sidechain_template,\n owner,\n adminFeeFraction,\n agents\n );\n address du = CloneLib.deployCodeAndInitUsingCreate2(CloneLib.cloneBytecode(data_union_mainnet_template), data, salt);\n emit MainnetDUCreated(du, sidechainAddress(du), owner, data_union_mainnet_template);\n return du;\n }\n}\n", + "sourcePath": "/home/heynow/streamr/data-union-solidity/contracts/DataUnionFactoryMainnet.sol", + "ast": { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/DataUnionFactoryMainnet.sol", + "exportedSymbols": { + "DataUnionFactoryMainnet": [ + 363 + ], + "IDataUnionMainnet": [ + 167 + ] + }, + "id": 364, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 156, + "literals": [ + "solidity", + "0.6", + ".6" + ], + "nodeType": "PragmaDirective", + "src": "0:22:1" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol", + "file": "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol", + "id": 157, + "nodeType": "ImportDirective", + "scope": 364, + "sourceUnit": 4283, + "src": "24:64:1", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "file": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "id": 158, + "nodeType": "ImportDirective", + "scope": 364, + "sourceUnit": 3642, + "src": "89:59:1", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/CloneLib.sol", + "file": "./CloneLib.sol", + "id": 159, + "nodeType": "ImportDirective", + "scope": 364, + "sourceUnit": 155, + "src": "149:24:1", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/IAMB.sol", + "file": "./IAMB.sol", + "id": 160, + "nodeType": "ImportDirective", + "scope": 364, + "sourceUnit": 2913, + "src": "174:20:1", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/ITokenMediator.sol", + "file": "./ITokenMediator.sol", + "id": 161, + "nodeType": "ImportDirective", + "scope": 364, + "sourceUnit": 2969, + "src": "195:30:1", + "symbolAliases": [], + "unitAlias": "" + }, + { + "abstract": false, + "baseContracts": [], + "contractDependencies": [], + "contractKind": "interface", + "documentation": null, + "fullyImplemented": false, + "id": 167, + "linearizedBaseContracts": [ + 167 + ], + "name": "IDataUnionMainnet", + "nodeType": "ContractDefinition", + "nodes": [ + { + "body": null, + "documentation": null, + "functionSelector": "37b43a94", + "id": 166, + "implemented": false, + "kind": "function", + "modifiers": [], + "name": "sidechainAddress", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 162, + "nodeType": "ParameterList", + "parameters": [], + "src": "286:2:1" + }, + "returnParameters": { + "id": 165, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 164, + "mutability": "mutable", + "name": "proxy", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 166, + "src": "312:13:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 163, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "312:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "311:15:1" + }, + "scope": 167, + "src": "261:66:1", + "stateMutability": "view", + "virtual": false, + "visibility": "external" + } + ], + "scope": 364, + "src": "227:102:1" + }, + { + "abstract": false, + "baseContracts": [], + "contractDependencies": [], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 363, + "linearizedBaseContracts": [ + 363 + ], + "name": "DataUnionFactoryMainnet", + "nodeType": "ContractDefinition", + "nodes": [ + { + "anonymous": false, + "documentation": null, + "id": 177, + "name": "MainnetDUCreated", + "nodeType": "EventDefinition", + "parameters": { + "id": 176, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 169, + "indexed": true, + "mutability": "mutable", + "name": "mainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 177, + "src": "394:23:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 168, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "394:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 171, + "indexed": true, + "mutability": "mutable", + "name": "sidechain", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 177, + "src": "419:25:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 170, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "419:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 173, + "indexed": true, + "mutability": "mutable", + "name": "owner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 177, + "src": "446:21:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 172, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "446:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 175, + "indexed": false, + "mutability": "mutable", + "name": "template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 177, + "src": "469:16:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 174, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "469:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "393:93:1" + }, + "src": "371:116:1" + }, + { + "constant": false, + "functionSelector": "015a0da0", + "id": 179, + "mutability": "mutable", + "name": "data_union_mainnet_template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 363, + "src": "493:42:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 178, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "493:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "e4a154a4", + "id": 181, + "mutability": "mutable", + "name": "data_union_sidechain_template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 363, + "src": "599:44:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 180, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "599:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "35813bc1", + "id": 183, + "mutability": "mutable", + "name": "data_union_sidechain_factory", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 363, + "src": "649:43:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 182, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "649:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "692199d4", + "id": 185, + "mutability": "mutable", + "name": "sidechain_maxgas", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 363, + "src": "698:31:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 184, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "698:7:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "1062b39a", + "id": 187, + "mutability": "mutable", + "name": "amb", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 363, + "src": "735:15:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + }, + "typeName": { + "contractScope": null, + "id": 186, + "name": "IAMB", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 2912, + "src": "735:4:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "c5a8c91f", + "id": 189, + "mutability": "mutable", + "name": "token_mediator", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 363, + "src": "756:36:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + }, + "typeName": { + "contractScope": null, + "id": 188, + "name": "ITokenMediator", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 2968, + "src": "756:14:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "fc0c546a", + "id": 191, + "mutability": "mutable", + "name": "token", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 363, + "src": "798:20:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 190, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "798:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "body": { + "id": 240, + "nodeType": "Block", + "src": "1121:398:1", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 208, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 206, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 191, + "src": "1131:5:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 207, + "name": "_token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 193, + "src": "1139:6:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "1131:14:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 209, + "nodeType": "ExpressionStatement", + "src": "1131:14:1" + }, + { + "expression": { + "argumentTypes": null, + "id": 214, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 210, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 189, + "src": "1155:14:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 212, + "name": "_token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 195, + "src": "1187:15:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 211, + "name": "ITokenMediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2968, + "src": "1172:14:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_ITokenMediator_$2968_$", + "typeString": "type(contract ITokenMediator)" + } + }, + "id": 213, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1172:31:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "src": "1155:48:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 215, + "nodeType": "ExpressionStatement", + "src": "1155:48:1" + }, + { + "expression": { + "argumentTypes": null, + "id": 218, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 216, + "name": "data_union_mainnet_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 179, + "src": "1213:27:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 217, + "name": "_data_union_mainnet_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 197, + "src": "1243:28:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "1213:58:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 219, + "nodeType": "ExpressionStatement", + "src": "1213:58:1" + }, + { + "expression": { + "argumentTypes": null, + "id": 222, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 220, + "name": "data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 181, + "src": "1281:29:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 221, + "name": "_data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 199, + "src": "1313:30:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "1281:62:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 223, + "nodeType": "ExpressionStatement", + "src": "1281:62:1" + }, + { + "expression": { + "argumentTypes": null, + "id": 226, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 224, + "name": "data_union_sidechain_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 183, + "src": "1353:28:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 225, + "name": "_data_union_sidechain_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 201, + "src": "1384:29:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "1353:60:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 227, + "nodeType": "ExpressionStatement", + "src": "1353:60:1" + }, + { + "expression": { + "argumentTypes": null, + "id": 234, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 228, + "name": "amb", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 187, + "src": "1423:3:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "expression": { + "argumentTypes": null, + "id": 230, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 189, + "src": "1434:14:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 231, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "bridgeContract", + "nodeType": "MemberAccess", + "referencedDeclaration": 2946, + "src": "1434:29:1", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$__$returns$_t_address_$", + "typeString": "function () view external returns (address)" + } + }, + "id": 232, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1434:31:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 229, + "name": "IAMB", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2912, + "src": "1429:4:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_IAMB_$2912_$", + "typeString": "type(contract IAMB)" + } + }, + "id": 233, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1429:37:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "src": "1423:43:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "id": 235, + "nodeType": "ExpressionStatement", + "src": "1423:43:1" + }, + { + "expression": { + "argumentTypes": null, + "id": 238, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 236, + "name": "sidechain_maxgas", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 185, + "src": "1476:16:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 237, + "name": "_sidechain_maxgas", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 203, + "src": "1495:17:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1476:36:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 239, + "nodeType": "ExpressionStatement", + "src": "1476:36:1" + } + ] + }, + "documentation": null, + "id": 241, + "implemented": true, + "kind": "constructor", + "modifiers": [], + "name": "", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 204, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 193, + "mutability": "mutable", + "name": "_token", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 241, + "src": "837:14:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 192, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "837:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 195, + "mutability": "mutable", + "name": "_token_mediator", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 241, + "src": "869:23:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 194, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "869:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 197, + "mutability": "mutable", + "name": "_data_union_mainnet_template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 241, + "src": "910:36:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 196, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "910:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 199, + "mutability": "mutable", + "name": "_data_union_sidechain_template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 241, + "src": "964:38:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 198, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "964:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 201, + "mutability": "mutable", + "name": "_data_union_sidechain_factory", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 241, + "src": "1020:37:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 200, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1020:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 203, + "mutability": "mutable", + "name": "_sidechain_maxgas", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 241, + "src": "1075:25:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 202, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1075:7:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "836:265:1" + }, + "returnParameters": { + "id": 205, + "nodeType": "ParameterList", + "parameters": [], + "src": "1121:0:1" + }, + "scope": 363, + "src": "825:694:1", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 261, + "nodeType": "Block", + "src": "1626:200:1", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 250, + "name": "data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 181, + "src": "1692:29:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 251, + "name": "data_union_sidechain_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 183, + "src": "1735:28:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 256, + "name": "mainet_address", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 243, + "src": "1793:14:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 255, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "1785:7:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_uint256_$", + "typeString": "type(uint256)" + }, + "typeName": { + "id": 254, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1785:7:1", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 257, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1785:23:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 253, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "1777:7:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes32_$", + "typeString": "type(bytes32)" + }, + "typeName": { + "id": 252, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "1777:7:1", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 258, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1777:32:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "expression": { + "argumentTypes": null, + "id": 248, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "1643:8:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 249, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "predictCloneAddressCreate2", + "nodeType": "MemberAccess", + "referencedDeclaration": 61, + "src": "1643:35:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_address_$_t_address_$_t_bytes32_$returns$_t_address_$", + "typeString": "function (address,address,bytes32) pure returns (address)" + } + }, + "id": 259, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1643:176:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "functionReturnParameters": 247, + "id": 260, + "nodeType": "Return", + "src": "1636:183:1" + } + ] + }, + "documentation": null, + "functionSelector": "17c2a98c", + "id": 262, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "sidechainAddress", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 244, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 243, + "mutability": "mutable", + "name": "mainet_address", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 262, + "src": "1552:22:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 242, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1552:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1551:24:1" + }, + "returnParameters": { + "id": 247, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 246, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 262, + "src": "1613:7:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 245, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1613:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1612:9:1" + }, + "scope": 363, + "src": "1526:300:1", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 294, + "nodeType": "Block", + "src": "1958:224:1", + "statements": [ + { + "assignments": [ + 272 + ], + "declarations": [ + { + "constant": false, + "id": 272, + "mutability": "mutable", + "name": "salt", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 294, + "src": "1968:12:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 271, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "1968:7:1", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 283, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 278, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 266, + "src": "2010:4:1", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + ], + "id": 277, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2004:5:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 276, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "2004:5:1", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 279, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2004:11:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "id": 280, + "name": "deployer", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 264, + "src": "2017:8:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "expression": { + "argumentTypes": null, + "id": 274, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "1993:3:1", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 275, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encode", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "1993:10:1", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencode_pure$__$returns$_t_bytes_memory_ptr_$", + "typeString": "function () pure returns (bytes memory)" + } + }, + "id": 281, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1993:33:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "id": 273, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "1983:9:1", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 282, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1983:44:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1968:59:1" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 286, + "name": "data_union_mainnet_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 179, + "src": "2093:27:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 289, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "2142:4:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionFactoryMainnet_$363", + "typeString": "contract DataUnionFactoryMainnet" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionFactoryMainnet_$363", + "typeString": "contract DataUnionFactoryMainnet" + } + ], + "id": 288, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2134:7:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 287, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2134:7:1", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 290, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2134:13:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 291, + "name": "salt", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 272, + "src": "2161:4:1", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "expression": { + "argumentTypes": null, + "id": 284, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "2044:8:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 285, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "predictCloneAddressCreate2", + "nodeType": "MemberAccess", + "referencedDeclaration": 61, + "src": "2044:35:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_address_$_t_address_$_t_bytes32_$returns$_t_address_$", + "typeString": "function (address,address,bytes32) pure returns (address)" + } + }, + "id": 292, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2044:131:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "functionReturnParameters": 270, + "id": 293, + "nodeType": "Return", + "src": "2037:138:1" + } + ] + }, + "documentation": null, + "functionSelector": "d4c31bd4", + "id": 295, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "mainnetAddress", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 267, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 264, + "mutability": "mutable", + "name": "deployer", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 295, + "src": "1870:16:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 263, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1870:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 266, + "mutability": "mutable", + "name": "name", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 295, + "src": "1888:18:1", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 265, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "1888:6:1", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1869:38:1" + }, + "returnParameters": { + "id": 270, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 269, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 295, + "src": "1945:7:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 268, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1945:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1944:9:1" + }, + "scope": 363, + "src": "1846:336:1", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 361, + "nodeType": "Block", + "src": "2705:690:1", + "statements": [ + { + "assignments": [ + 310 + ], + "declarations": [ + { + "constant": false, + "id": 310, + "mutability": "mutable", + "name": "salt", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 361, + "src": "2715:12:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 309, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "2715:7:1", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 322, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 316, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 304, + "src": "2757:4:1", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + ], + "id": 315, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2751:5:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 314, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "2751:5:1", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 317, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2751:11:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 318, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "2764:3:1", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 319, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "2764:10:1", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "expression": { + "argumentTypes": null, + "id": 312, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "2740:3:1", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 313, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encode", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "2740:10:1", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencode_pure$__$returns$_t_bytes_memory_ptr_$", + "typeString": "function () pure returns (bytes memory)" + } + }, + "id": 320, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2740:35:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "id": 311, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "2730:9:1", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 321, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2730:46:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "2715:61:1" + }, + { + "assignments": [ + 324 + ], + "declarations": [ + { + "constant": false, + "id": 324, + "mutability": "mutable", + "name": "data", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 361, + "src": "2786:17:1", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 323, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "2786:5:1", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 337, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "696e697469616c697a6528616464726573732c616464726573732c616464726573732c75696e743235362c616464726573732c616464726573732c75696e743235362c616464726573735b5d29", + "id": 327, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2830:79:1", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_fb6470c99ff27472c874d5b7a3935111326af310466bebb9ef42dc6569bc2cec", + "typeString": "literal_string \"initialize(address,address,address,uint256,address,address,uint256,address[])\"" + }, + "value": "initialize(address,address,address,uint256,address,address,uint256,address[])" + }, + { + "argumentTypes": null, + "id": 328, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 191, + "src": "2923:5:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 329, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 189, + "src": "2942:14:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + { + "argumentTypes": null, + "id": 330, + "name": "data_union_sidechain_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 183, + "src": "2970:28:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 331, + "name": "sidechain_maxgas", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 185, + "src": "3012:16:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 332, + "name": "data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 181, + "src": "3042:29:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 333, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 297, + "src": "3085:5:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 334, + "name": "adminFeeFraction", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 299, + "src": "3104:16:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 335, + "name": "agents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 302, + "src": "3134:6:1", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_fb6470c99ff27472c874d5b7a3935111326af310466bebb9ef42dc6569bc2cec", + "typeString": "literal_string \"initialize(address,address,address,uint256,address,address,uint256,address[])\"" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + ], + "expression": { + "argumentTypes": null, + "id": 325, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "2806:3:1", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 326, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encodeWithSignature", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "2806:23:1", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencodewithsignature_pure$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (string memory) pure returns (bytes memory)" + } + }, + "id": 336, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2806:344:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "2786:364:1" + }, + { + "assignments": [ + 339 + ], + "declarations": [ + { + "constant": false, + "id": 339, + "mutability": "mutable", + "name": "du", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 361, + "src": "3160:10:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 338, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3160:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 349, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 344, + "name": "data_union_mainnet_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 179, + "src": "3235:27:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "expression": { + "argumentTypes": null, + "id": 342, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "3212:8:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 343, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "cloneBytecode", + "nodeType": "MemberAccess", + "referencedDeclaration": 18, + "src": "3212:22:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_address_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (address) pure returns (bytes memory)" + } + }, + "id": 345, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3212:51:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "id": 346, + "name": "data", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 324, + "src": "3265:4:1", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "id": 347, + "name": "salt", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 310, + "src": "3271:4:1", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "expression": { + "argumentTypes": null, + "id": 340, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "3173:8:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 341, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "deployCodeAndInitUsingCreate2", + "nodeType": "MemberAccess", + "referencedDeclaration": 108, + "src": "3173:38:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_bytes_memory_ptr_$_t_bytes_memory_ptr_$_t_bytes32_$returns$_t_address_payable_$", + "typeString": "function (bytes memory,bytes memory,bytes32) returns (address payable)" + } + }, + "id": 348, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3173:103:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "3160:116:1" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 351, + "name": "du", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 339, + "src": "3308:2:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 353, + "name": "du", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 339, + "src": "3329:2:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 352, + "name": "sidechainAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 262, + "src": "3312:16:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_address_$", + "typeString": "function (address) view returns (address)" + } + }, + "id": 354, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3312:20:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 355, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 297, + "src": "3334:5:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 356, + "name": "data_union_mainnet_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 179, + "src": "3341:27:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 350, + "name": "MainnetDUCreated", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 177, + "src": "3291:16:1", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_address_$_t_address_$_t_address_$returns$__$", + "typeString": "function (address,address,address,address)" + } + }, + "id": 357, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3291:78:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 358, + "nodeType": "EmitStatement", + "src": "3286:83:1" + }, + { + "expression": { + "argumentTypes": null, + "id": 359, + "name": "du", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 339, + "src": "3386:2:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "functionReturnParameters": 308, + "id": 360, + "nodeType": "Return", + "src": "3379:9:1" + } + ] + }, + "documentation": null, + "functionSelector": "cb8a191b", + "id": 362, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "deployNewDataUnion", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 305, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 297, + "mutability": "mutable", + "name": "owner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 362, + "src": "2574:13:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 296, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2574:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 299, + "mutability": "mutable", + "name": "adminFeeFraction", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 362, + "src": "2589:24:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 298, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "2589:7:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 302, + "mutability": "mutable", + "name": "agents", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 362, + "src": "2615:23:1", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 300, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2615:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 301, + "length": null, + "nodeType": "ArrayTypeName", + "src": "2615:9:1", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 304, + "mutability": "mutable", + "name": "name", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 362, + "src": "2640:18:1", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 303, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "2640:6:1", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2573:86:1" + }, + "returnParameters": { + "id": 308, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 307, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 362, + "src": "2692:7:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 306, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2692:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2691:9:1" + }, + "scope": 363, + "src": "2546:849:1", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + } + ], + "scope": 364, + "src": "332:3065:1" + } + ], + "src": "0:3398:1" + }, + "legacyAST": { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/DataUnionFactoryMainnet.sol", + "exportedSymbols": { + "DataUnionFactoryMainnet": [ + 363 + ], + "IDataUnionMainnet": [ + 167 + ] + }, + "id": 364, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 156, + "literals": [ + "solidity", + "0.6", + ".6" + ], + "nodeType": "PragmaDirective", + "src": "0:22:1" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol", + "file": "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol", + "id": 157, + "nodeType": "ImportDirective", + "scope": 364, + "sourceUnit": 4283, + "src": "24:64:1", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "file": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "id": 158, + "nodeType": "ImportDirective", + "scope": 364, + "sourceUnit": 3642, + "src": "89:59:1", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/CloneLib.sol", + "file": "./CloneLib.sol", + "id": 159, + "nodeType": "ImportDirective", + "scope": 364, + "sourceUnit": 155, + "src": "149:24:1", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/IAMB.sol", + "file": "./IAMB.sol", + "id": 160, + "nodeType": "ImportDirective", + "scope": 364, + "sourceUnit": 2913, + "src": "174:20:1", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/ITokenMediator.sol", + "file": "./ITokenMediator.sol", + "id": 161, + "nodeType": "ImportDirective", + "scope": 364, + "sourceUnit": 2969, + "src": "195:30:1", + "symbolAliases": [], + "unitAlias": "" + }, + { + "abstract": false, + "baseContracts": [], + "contractDependencies": [], + "contractKind": "interface", + "documentation": null, + "fullyImplemented": false, + "id": 167, + "linearizedBaseContracts": [ + 167 + ], + "name": "IDataUnionMainnet", + "nodeType": "ContractDefinition", + "nodes": [ + { + "body": null, + "documentation": null, + "functionSelector": "37b43a94", + "id": 166, + "implemented": false, + "kind": "function", + "modifiers": [], + "name": "sidechainAddress", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 162, + "nodeType": "ParameterList", + "parameters": [], + "src": "286:2:1" + }, + "returnParameters": { + "id": 165, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 164, + "mutability": "mutable", + "name": "proxy", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 166, + "src": "312:13:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 163, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "312:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "311:15:1" + }, + "scope": 167, + "src": "261:66:1", + "stateMutability": "view", + "virtual": false, + "visibility": "external" + } + ], + "scope": 364, + "src": "227:102:1" + }, + { + "abstract": false, + "baseContracts": [], + "contractDependencies": [], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 363, + "linearizedBaseContracts": [ + 363 + ], + "name": "DataUnionFactoryMainnet", + "nodeType": "ContractDefinition", + "nodes": [ + { + "anonymous": false, + "documentation": null, + "id": 177, + "name": "MainnetDUCreated", + "nodeType": "EventDefinition", + "parameters": { + "id": 176, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 169, + "indexed": true, + "mutability": "mutable", + "name": "mainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 177, + "src": "394:23:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 168, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "394:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 171, + "indexed": true, + "mutability": "mutable", + "name": "sidechain", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 177, + "src": "419:25:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 170, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "419:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 173, + "indexed": true, + "mutability": "mutable", + "name": "owner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 177, + "src": "446:21:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 172, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "446:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 175, + "indexed": false, + "mutability": "mutable", + "name": "template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 177, + "src": "469:16:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 174, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "469:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "393:93:1" + }, + "src": "371:116:1" + }, + { + "constant": false, + "functionSelector": "015a0da0", + "id": 179, + "mutability": "mutable", + "name": "data_union_mainnet_template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 363, + "src": "493:42:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 178, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "493:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "e4a154a4", + "id": 181, + "mutability": "mutable", + "name": "data_union_sidechain_template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 363, + "src": "599:44:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 180, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "599:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "35813bc1", + "id": 183, + "mutability": "mutable", + "name": "data_union_sidechain_factory", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 363, + "src": "649:43:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 182, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "649:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "692199d4", + "id": 185, + "mutability": "mutable", + "name": "sidechain_maxgas", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 363, + "src": "698:31:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 184, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "698:7:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "1062b39a", + "id": 187, + "mutability": "mutable", + "name": "amb", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 363, + "src": "735:15:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + }, + "typeName": { + "contractScope": null, + "id": 186, + "name": "IAMB", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 2912, + "src": "735:4:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "c5a8c91f", + "id": 189, + "mutability": "mutable", + "name": "token_mediator", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 363, + "src": "756:36:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + }, + "typeName": { + "contractScope": null, + "id": 188, + "name": "ITokenMediator", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 2968, + "src": "756:14:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "fc0c546a", + "id": 191, + "mutability": "mutable", + "name": "token", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 363, + "src": "798:20:1", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 190, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "798:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "body": { + "id": 240, + "nodeType": "Block", + "src": "1121:398:1", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 208, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 206, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 191, + "src": "1131:5:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 207, + "name": "_token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 193, + "src": "1139:6:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "1131:14:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 209, + "nodeType": "ExpressionStatement", + "src": "1131:14:1" + }, + { + "expression": { + "argumentTypes": null, + "id": 214, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 210, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 189, + "src": "1155:14:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 212, + "name": "_token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 195, + "src": "1187:15:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 211, + "name": "ITokenMediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2968, + "src": "1172:14:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_ITokenMediator_$2968_$", + "typeString": "type(contract ITokenMediator)" + } + }, + "id": 213, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1172:31:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "src": "1155:48:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 215, + "nodeType": "ExpressionStatement", + "src": "1155:48:1" + }, + { + "expression": { + "argumentTypes": null, + "id": 218, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 216, + "name": "data_union_mainnet_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 179, + "src": "1213:27:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 217, + "name": "_data_union_mainnet_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 197, + "src": "1243:28:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "1213:58:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 219, + "nodeType": "ExpressionStatement", + "src": "1213:58:1" + }, + { + "expression": { + "argumentTypes": null, + "id": 222, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 220, + "name": "data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 181, + "src": "1281:29:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 221, + "name": "_data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 199, + "src": "1313:30:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "1281:62:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 223, + "nodeType": "ExpressionStatement", + "src": "1281:62:1" + }, + { + "expression": { + "argumentTypes": null, + "id": 226, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 224, + "name": "data_union_sidechain_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 183, + "src": "1353:28:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 225, + "name": "_data_union_sidechain_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 201, + "src": "1384:29:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "1353:60:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 227, + "nodeType": "ExpressionStatement", + "src": "1353:60:1" + }, + { + "expression": { + "argumentTypes": null, + "id": 234, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 228, + "name": "amb", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 187, + "src": "1423:3:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "expression": { + "argumentTypes": null, + "id": 230, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 189, + "src": "1434:14:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 231, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "bridgeContract", + "nodeType": "MemberAccess", + "referencedDeclaration": 2946, + "src": "1434:29:1", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$__$returns$_t_address_$", + "typeString": "function () view external returns (address)" + } + }, + "id": 232, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1434:31:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 229, + "name": "IAMB", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2912, + "src": "1429:4:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_IAMB_$2912_$", + "typeString": "type(contract IAMB)" + } + }, + "id": 233, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1429:37:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "src": "1423:43:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "id": 235, + "nodeType": "ExpressionStatement", + "src": "1423:43:1" + }, + { + "expression": { + "argumentTypes": null, + "id": 238, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 236, + "name": "sidechain_maxgas", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 185, + "src": "1476:16:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 237, + "name": "_sidechain_maxgas", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 203, + "src": "1495:17:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1476:36:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 239, + "nodeType": "ExpressionStatement", + "src": "1476:36:1" + } + ] + }, + "documentation": null, + "id": 241, + "implemented": true, + "kind": "constructor", + "modifiers": [], + "name": "", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 204, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 193, + "mutability": "mutable", + "name": "_token", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 241, + "src": "837:14:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 192, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "837:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 195, + "mutability": "mutable", + "name": "_token_mediator", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 241, + "src": "869:23:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 194, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "869:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 197, + "mutability": "mutable", + "name": "_data_union_mainnet_template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 241, + "src": "910:36:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 196, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "910:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 199, + "mutability": "mutable", + "name": "_data_union_sidechain_template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 241, + "src": "964:38:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 198, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "964:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 201, + "mutability": "mutable", + "name": "_data_union_sidechain_factory", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 241, + "src": "1020:37:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 200, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1020:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 203, + "mutability": "mutable", + "name": "_sidechain_maxgas", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 241, + "src": "1075:25:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 202, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1075:7:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "836:265:1" + }, + "returnParameters": { + "id": 205, + "nodeType": "ParameterList", + "parameters": [], + "src": "1121:0:1" + }, + "scope": 363, + "src": "825:694:1", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 261, + "nodeType": "Block", + "src": "1626:200:1", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 250, + "name": "data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 181, + "src": "1692:29:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 251, + "name": "data_union_sidechain_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 183, + "src": "1735:28:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 256, + "name": "mainet_address", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 243, + "src": "1793:14:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 255, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "1785:7:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_uint256_$", + "typeString": "type(uint256)" + }, + "typeName": { + "id": 254, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1785:7:1", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 257, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1785:23:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 253, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "1777:7:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes32_$", + "typeString": "type(bytes32)" + }, + "typeName": { + "id": 252, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "1777:7:1", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 258, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1777:32:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "expression": { + "argumentTypes": null, + "id": 248, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "1643:8:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 249, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "predictCloneAddressCreate2", + "nodeType": "MemberAccess", + "referencedDeclaration": 61, + "src": "1643:35:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_address_$_t_address_$_t_bytes32_$returns$_t_address_$", + "typeString": "function (address,address,bytes32) pure returns (address)" + } + }, + "id": 259, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1643:176:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "functionReturnParameters": 247, + "id": 260, + "nodeType": "Return", + "src": "1636:183:1" + } + ] + }, + "documentation": null, + "functionSelector": "17c2a98c", + "id": 262, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "sidechainAddress", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 244, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 243, + "mutability": "mutable", + "name": "mainet_address", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 262, + "src": "1552:22:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 242, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1552:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1551:24:1" + }, + "returnParameters": { + "id": 247, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 246, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 262, + "src": "1613:7:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 245, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1613:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1612:9:1" + }, + "scope": 363, + "src": "1526:300:1", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 294, + "nodeType": "Block", + "src": "1958:224:1", + "statements": [ + { + "assignments": [ + 272 + ], + "declarations": [ + { + "constant": false, + "id": 272, + "mutability": "mutable", + "name": "salt", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 294, + "src": "1968:12:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 271, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "1968:7:1", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 283, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 278, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 266, + "src": "2010:4:1", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + ], + "id": 277, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2004:5:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 276, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "2004:5:1", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 279, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2004:11:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "id": 280, + "name": "deployer", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 264, + "src": "2017:8:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "expression": { + "argumentTypes": null, + "id": 274, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "1993:3:1", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 275, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encode", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "1993:10:1", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencode_pure$__$returns$_t_bytes_memory_ptr_$", + "typeString": "function () pure returns (bytes memory)" + } + }, + "id": 281, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1993:33:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "id": 273, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "1983:9:1", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 282, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1983:44:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "1968:59:1" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 286, + "name": "data_union_mainnet_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 179, + "src": "2093:27:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 289, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "2142:4:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionFactoryMainnet_$363", + "typeString": "contract DataUnionFactoryMainnet" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionFactoryMainnet_$363", + "typeString": "contract DataUnionFactoryMainnet" + } + ], + "id": 288, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2134:7:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 287, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2134:7:1", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 290, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2134:13:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 291, + "name": "salt", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 272, + "src": "2161:4:1", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "expression": { + "argumentTypes": null, + "id": 284, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "2044:8:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 285, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "predictCloneAddressCreate2", + "nodeType": "MemberAccess", + "referencedDeclaration": 61, + "src": "2044:35:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_address_$_t_address_$_t_bytes32_$returns$_t_address_$", + "typeString": "function (address,address,bytes32) pure returns (address)" + } + }, + "id": 292, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2044:131:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "functionReturnParameters": 270, + "id": 293, + "nodeType": "Return", + "src": "2037:138:1" + } + ] + }, + "documentation": null, + "functionSelector": "d4c31bd4", + "id": 295, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "mainnetAddress", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 267, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 264, + "mutability": "mutable", + "name": "deployer", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 295, + "src": "1870:16:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 263, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1870:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 266, + "mutability": "mutable", + "name": "name", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 295, + "src": "1888:18:1", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 265, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "1888:6:1", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1869:38:1" + }, + "returnParameters": { + "id": 270, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 269, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 295, + "src": "1945:7:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 268, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1945:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1944:9:1" + }, + "scope": 363, + "src": "1846:336:1", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 361, + "nodeType": "Block", + "src": "2705:690:1", + "statements": [ + { + "assignments": [ + 310 + ], + "declarations": [ + { + "constant": false, + "id": 310, + "mutability": "mutable", + "name": "salt", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 361, + "src": "2715:12:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 309, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "2715:7:1", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 322, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 316, + "name": "name", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 304, + "src": "2757:4:1", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string memory" + } + ], + "id": 315, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2751:5:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes_storage_ptr_$", + "typeString": "type(bytes storage pointer)" + }, + "typeName": { + "id": 314, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "2751:5:1", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 317, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2751:11:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 318, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "2764:3:1", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 319, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "2764:10:1", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "expression": { + "argumentTypes": null, + "id": 312, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "2740:3:1", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 313, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encode", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "2740:10:1", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencode_pure$__$returns$_t_bytes_memory_ptr_$", + "typeString": "function () pure returns (bytes memory)" + } + }, + "id": 320, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2740:35:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "id": 311, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "2730:9:1", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 321, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2730:46:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "2715:61:1" + }, + { + "assignments": [ + 324 + ], + "declarations": [ + { + "constant": false, + "id": 324, + "mutability": "mutable", + "name": "data", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 361, + "src": "2786:17:1", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 323, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "2786:5:1", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 337, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "696e697469616c697a6528616464726573732c616464726573732c616464726573732c75696e743235362c616464726573732c616464726573732c75696e743235362c616464726573735b5d29", + "id": 327, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2830:79:1", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_fb6470c99ff27472c874d5b7a3935111326af310466bebb9ef42dc6569bc2cec", + "typeString": "literal_string \"initialize(address,address,address,uint256,address,address,uint256,address[])\"" + }, + "value": "initialize(address,address,address,uint256,address,address,uint256,address[])" + }, + { + "argumentTypes": null, + "id": 328, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 191, + "src": "2923:5:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 329, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 189, + "src": "2942:14:1", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + { + "argumentTypes": null, + "id": 330, + "name": "data_union_sidechain_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 183, + "src": "2970:28:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 331, + "name": "sidechain_maxgas", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 185, + "src": "3012:16:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 332, + "name": "data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 181, + "src": "3042:29:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 333, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 297, + "src": "3085:5:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 334, + "name": "adminFeeFraction", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 299, + "src": "3104:16:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 335, + "name": "agents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 302, + "src": "3134:6:1", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_fb6470c99ff27472c874d5b7a3935111326af310466bebb9ef42dc6569bc2cec", + "typeString": "literal_string \"initialize(address,address,address,uint256,address,address,uint256,address[])\"" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + ], + "expression": { + "argumentTypes": null, + "id": 325, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "2806:3:1", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 326, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encodeWithSignature", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "2806:23:1", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencodewithsignature_pure$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (string memory) pure returns (bytes memory)" + } + }, + "id": 336, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2806:344:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "2786:364:1" + }, + { + "assignments": [ + 339 + ], + "declarations": [ + { + "constant": false, + "id": 339, + "mutability": "mutable", + "name": "du", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 361, + "src": "3160:10:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 338, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3160:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 349, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 344, + "name": "data_union_mainnet_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 179, + "src": "3235:27:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "expression": { + "argumentTypes": null, + "id": 342, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "3212:8:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 343, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "cloneBytecode", + "nodeType": "MemberAccess", + "referencedDeclaration": 18, + "src": "3212:22:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_address_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (address) pure returns (bytes memory)" + } + }, + "id": 345, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3212:51:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "id": 346, + "name": "data", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 324, + "src": "3265:4:1", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "id": 347, + "name": "salt", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 310, + "src": "3271:4:1", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "expression": { + "argumentTypes": null, + "id": 340, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "3173:8:1", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 341, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "deployCodeAndInitUsingCreate2", + "nodeType": "MemberAccess", + "referencedDeclaration": 108, + "src": "3173:38:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_bytes_memory_ptr_$_t_bytes_memory_ptr_$_t_bytes32_$returns$_t_address_payable_$", + "typeString": "function (bytes memory,bytes memory,bytes32) returns (address payable)" + } + }, + "id": 348, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3173:103:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "3160:116:1" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 351, + "name": "du", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 339, + "src": "3308:2:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 353, + "name": "du", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 339, + "src": "3329:2:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 352, + "name": "sidechainAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 262, + "src": "3312:16:1", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_address_$", + "typeString": "function (address) view returns (address)" + } + }, + "id": 354, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3312:20:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 355, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 297, + "src": "3334:5:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 356, + "name": "data_union_mainnet_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 179, + "src": "3341:27:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 350, + "name": "MainnetDUCreated", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 177, + "src": "3291:16:1", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_address_$_t_address_$_t_address_$returns$__$", + "typeString": "function (address,address,address,address)" + } + }, + "id": 357, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3291:78:1", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 358, + "nodeType": "EmitStatement", + "src": "3286:83:1" + }, + { + "expression": { + "argumentTypes": null, + "id": 359, + "name": "du", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 339, + "src": "3386:2:1", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "functionReturnParameters": 308, + "id": 360, + "nodeType": "Return", + "src": "3379:9:1" + } + ] + }, + "documentation": null, + "functionSelector": "cb8a191b", + "id": 362, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "deployNewDataUnion", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 305, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 297, + "mutability": "mutable", + "name": "owner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 362, + "src": "2574:13:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 296, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2574:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 299, + "mutability": "mutable", + "name": "adminFeeFraction", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 362, + "src": "2589:24:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 298, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "2589:7:1", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 302, + "mutability": "mutable", + "name": "agents", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 362, + "src": "2615:23:1", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 300, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2615:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 301, + "length": null, + "nodeType": "ArrayTypeName", + "src": "2615:9:1", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 304, + "mutability": "mutable", + "name": "name", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 362, + "src": "2640:18:1", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_string_memory_ptr", + "typeString": "string" + }, + "typeName": { + "id": 303, + "name": "string", + "nodeType": "ElementaryTypeName", + "src": "2640:6:1", + "typeDescriptions": { + "typeIdentifier": "t_string_storage_ptr", + "typeString": "string" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2573:86:1" + }, + "returnParameters": { + "id": 308, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 307, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 362, + "src": "2692:7:1", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 306, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2692:7:1", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2691:9:1" + }, + "scope": 363, + "src": "2546:849:1", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + } + ], + "scope": 364, + "src": "332:3065:1" + } + ], + "src": "0:3398:1" + }, + "compiler": { + "name": "solc", + "version": "0.6.6+commit.6c089d02.Emscripten.clang" + }, + "networks": {}, + "schemaVersion": "3.2.3", + "updatedAt": "2020-12-09T11:44:39.453Z", + "devdoc": { + "methods": {} + }, + "userdoc": { + "methods": {} + } +} \ No newline at end of file diff --git a/contracts/DataUnionFactorySidechain.json b/contracts/DataUnionFactorySidechain.json new file mode 100644 index 000000000..478df27ad --- /dev/null +++ b/contracts/DataUnionFactorySidechain.json @@ -0,0 +1,8461 @@ +{ + "contractName": "DataUnionFactorySidechain", + "abi": [ + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_token_mediator", + "type": "address" + }, + { + "internalType": "address", + "name": "_data_union_sidechain_template", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amountWei", + "type": "uint256" + } + ], + "name": "DUInitialEthSent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amountWei", + "type": "uint256" + } + ], + "name": "OwnerInitialEthSent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "mainnet", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "sidenet", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "owner", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "template", + "type": "address" + } + ], + "name": "SidechainDUCreated", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "UpdateDefaultNewMemberInitialEth", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "UpdateNewDUInitialEth", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "UpdateNewDUOwnerInitialEth", + "type": "event" + }, + { + "inputs": [], + "name": "amb", + "outputs": [ + { + "internalType": "contract IAMB", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "data_union_sidechain_template", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "defaultNewMemberEth", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "newDUInitialEth", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "newDUOwnerInitialEth", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token_mediator", + "outputs": [ + { + "internalType": "contract ITokenMediator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "val", + "type": "uint256" + } + ], + "name": "setNewDUInitialEth", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "val", + "type": "uint256" + } + ], + "name": "setNewDUOwnerInitialEth", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "val", + "type": "uint256" + } + ], + "name": "setNewMemberInitialEth", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "mainet_address", + "type": "address" + } + ], + "name": "sidechainAddress", + "outputs": [ + { + "internalType": "address", + "name": "proxy", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "owner", + "type": "address" + }, + { + "internalType": "address[]", + "name": "agents", + "type": "address[]" + } + ], + "name": "deployNewDUSidechain", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "metadata": "{\"compiler\":{\"version\":\"0.6.6+commit.6c089d02\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_token_mediator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_data_union_sidechain_template\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountWei\",\"type\":\"uint256\"}],\"name\":\"DUInitialEthSent\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountWei\",\"type\":\"uint256\"}],\"name\":\"OwnerInitialEthSent\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"mainnet\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sidenet\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"template\",\"type\":\"address\"}],\"name\":\"SidechainDUCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"UpdateDefaultNewMemberInitialEth\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"UpdateNewDUInitialEth\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"UpdateNewDUOwnerInitialEth\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"amb\",\"outputs\":[{\"internalType\":\"contract IAMB\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"data_union_sidechain_template\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"defaultNewMemberEth\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"agents\",\"type\":\"address[]\"}],\"name\":\"deployNewDUSidechain\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"newDUInitialEth\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"newDUOwnerInitialEth\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"val\",\"type\":\"uint256\"}],\"name\":\"setNewDUInitialEth\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"val\",\"type\":\"uint256\"}],\"name\":\"setNewDUOwnerInitialEth\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"val\",\"type\":\"uint256\"}],\"name\":\"setNewMemberInitialEth\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"mainet_address\",\"type\":\"address\"}],\"name\":\"sidechainAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"proxy\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token_mediator\",\"outputs\":[{\"internalType\":\"contract ITokenMediator\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"methods\":{\"claimOwnership()\":{\"details\":\"Allows the pendingOwner address to finalize the transfer.\"},\"transferOwnership(address)\":{\"details\":\"Allows the current owner to set the pendingOwner address.\",\"params\":{\"newOwner\":\"The address to transfer ownership to.\"}}}},\"userdoc\":{\"methods\":{}}},\"settings\":{\"compilationTarget\":{\"/home/heynow/streamr/data-union-solidity/contracts/DataUnionFactorySidechain.sol\":\"DataUnionFactorySidechain\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/heynow/streamr/data-union-solidity/contracts/CloneLib.sol\":{\"keccak256\":\"0xf9082fe41a9e05b37cf87f0d1d67ca16d32c4100a2a27a6fc9d8f3f8364a7486\",\"urls\":[\"bzz-raw://b928f45b1904c399db9508c093604531bd215afeb4686827ab46daa7aee1938e\",\"dweb:/ipfs/QmdnBUvpCDc93fHZe7o6bitGRZo4PFTx9NprgziqHsVHFw\"]},\"/home/heynow/streamr/data-union-solidity/contracts/DataUnionFactorySidechain.sol\":{\"keccak256\":\"0xc288c62f30298711897bd47d5f2a08e12542f59e5bbf55de066b2bd865276921\",\"urls\":[\"bzz-raw://144897a0f71e8440f8c2ab182cd4589c1f588dae3d8e8879a4902da1bd622914\",\"dweb:/ipfs/QmVpdWc3bK5QEw3hF5FnNG9DTuzMKTzjAmTtVEcu2WDSFM\"]},\"/home/heynow/streamr/data-union-solidity/contracts/IAMB.sol\":{\"keccak256\":\"0x63b46d3087a8ba558cb36d99ffe5b2f0241446adc4c8df4219ce86f97727e168\",\"urls\":[\"bzz-raw://66d9c8c817e90b25f28b3d79a69b440018b49633a7bbaf6fe4d1788b2ff5e6eb\",\"dweb:/ipfs/QmSdrqpaZ3R3NGFuF6jnbo8cCgV5ghdW5iWrnV59qavYP2\"]},\"/home/heynow/streamr/data-union-solidity/contracts/ITokenMediator.sol\":{\"keccak256\":\"0x1f21e943f4e125dd1c4353db29880b63f8fa7a299ba862432c8a4e14807873e2\",\"urls\":[\"bzz-raw://4ddc233243efce6c2582b63e3be28c4f90f32d60b5d4b6fe8331106d970a8435\",\"dweb:/ipfs/QmQzLwvxU3ixfLbdpuYdpqvW61xjPbnXDTaaWNjRJUhvSK\"]},\"/home/heynow/streamr/data-union-solidity/contracts/Ownable.sol\":{\"keccak256\":\"0x0d96dac82f3bf17d2cdc90863a61c8957622c305bc3bbf9cd8f47facfa71dea1\",\"urls\":[\"bzz-raw://1cd621b81754be8073fc89bd2bc864e7b0169ba5f153edc5b9212a19f4890aa3\",\"dweb:/ipfs/QmcE5HTf3pptr2s7oiDqYtotCLrb736g8N7YyRZj8k5mHg\"]},\"openzeppelin-solidity/contracts/math/SafeMath.sol\":{\"keccak256\":\"0x9a9cf02622cd7a64261b10534fc3260449da25c98c9e96d1b4ae8110a20e5806\",\"urls\":[\"bzz-raw://2df142592d1dc267d9549049ee3317fa190d2f87eaa565f86ab05ec83f7ab8f5\",\"dweb:/ipfs/QmSkJtcfWo7c42KnL5hho6GFxK6HRNV91XABx1P7xDtfLV\"]},\"openzeppelin-solidity/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0x5c26b39d26f7ed489e555d955dcd3e01872972e71fdd1528e93ec164e4f23385\",\"urls\":[\"bzz-raw://efdc632af6960cf865dbc113665ea1f5b90eab75cc40ec062b2f6ae6da582017\",\"dweb:/ipfs/QmfAZFDuG62vxmAN9DnXApv7e7PMzPqi4RkqqZHLMSQiY5\"]}},\"version\":1}", + "bytecode": "0x608060405234801561001057600080fd5b50604051610d96380380610d968339818101604052606081101561003357600080fd5b50805160208083015160409384015160008054336001600160a01b0319918216179091556008805482166001600160a01b0380881691909117909155600480548316828616178082556002805490941683861617909355875163cd59658360e01b81529751969794969395929091169363cd5965839381830193929091829003018186803b1580156100c457600080fd5b505afa1580156100d8573d6000803e3d6000fd5b505050506040513d60208110156100ee57600080fd5b5051600380546001600160a01b039092166001600160a01b0319909216919091179055505050610c73806101236000396000f3fe6080604052600436106100f75760003560e01c8063c5a8c91f1161008a578063f0ef0b0611610059578063f0ef0b0614610322578063f2fde38b1461034c578063f7c1329e1461037f578063fc0c546a14610394576100fe565b8063c5a8c91f146102ce578063e22ab5ae146102e3578063e30c3978146102f8578063e4a154a41461030d576100fe565b80634e51a863116100c65780634e51a863146102535780634e71e0c81461027d5780638da5cb5b14610292578063afc6224b146102a7576100fe565b80631062b39a1461010357806317c2a98c14610134578063325ff66f1461016757806337dd8b0514610227576100fe565b366100fe57005b600080fd5b34801561010f57600080fd5b506101186103a9565b604080516001600160a01b039092168252519081900360200190f35b34801561014057600080fd5b506101186004803603602081101561015757600080fd5b50356001600160a01b03166103b8565b34801561017357600080fd5b506101186004803603604081101561018a57600080fd5b6001600160a01b0382351691908101906040810160208201356401000000008111156101b557600080fd5b8201836020820111156101c757600080fd5b803590602001918460208302840111640100000000831117156101e957600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506103dd945050505050565b34801561023357600080fd5b506102516004803603602081101561024a57600080fd5b50356106dd565b005b34801561025f57600080fd5b506102516004803603602081101561027657600080fd5b5035610773565b34801561028957600080fd5b50610251610808565b34801561029e57600080fd5b506101186108be565b3480156102b357600080fd5b506102bc6108cd565b60408051918252519081900360200190f35b3480156102da57600080fd5b506101186108d3565b3480156102ef57600080fd5b506102bc6108e2565b34801561030457600080fd5b506101186108e8565b34801561031957600080fd5b506101186108f7565b34801561032e57600080fd5b506102516004803603602081101561034557600080fd5b5035610906565b34801561035857600080fd5b506102516004803603602081101561036f57600080fd5b50356001600160a01b031661099b565b34801561038b57600080fd5b506102bc610a08565b3480156103a057600080fd5b50610118610a0e565b6003546001600160a01b031681565b6002546000906103d7906001600160a01b039081169030908516610a1d565b92915050565b6003546000906001600160a01b0316331461042a576040805162461bcd60e51b815260206004820152600860248201526737b7363cafa0a6a160c11b604482015290519081900360640190fd5b6003546040805163d67bdd2560e01b815290516000926001600160a01b03169163d67bdd25916004808301926020929190829003018186803b15801561046f57600080fd5b505afa158015610483573d6000803e3d6000fd5b505050506040513d602081101561049957600080fd5b50516008546004546007546040516001600160a01b0389811660248301908152948116604483018190529381166084830181905290861660a4830181905260c4830184905260c0606484019081528a5160e48501528a5197985090966060968c96958c958b949193610104909101906020888101910280838360005b8381101561052d578181015183820152602001610515565b50506040805193909501838103601f190184529094525060208101805163015c7f5160e01b6001600160e01b03909116179052600254909b5060009a50610592995061058b98506001600160a01b03169650610a8995505050505050565b8385610adb565b600254604080516001600160a01b0392831681529051929350818a1692828516928816917f90d0a5d098b9a181ff8ddc866f840cc210e5b91eaf27bc267d5822a0deafad25919081900360200190a4600554158015906105f457506005544710155b1561065a576005546040516001600160a01b0383169180156108fc02916000818181858888f193505050501561065a5760055460408051918252517f517165f169759cdb94227d1c50f4f47895eb099a7f04a780f519bf1739face6f9181900360200190a15b6006541580159061066d57506006544710155b156106d3576006546040516001600160a01b0389169180156108fc02916000818181858888f19350505050156106d35760065460408051918252517f69e30c0bf438d0d3e0afb7f68d57ef394a0d5e8712f82fa00aa599e42574bc2a9181900360200190a15b9695505050505050565b6000546001600160a01b03163314610728576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b60075481141561073757610770565b60078190556040805182815290517f7a78bdfbfb2e909f35c05c77e80038cfd0a22c704748eba8b1d20aab76cd5d9c9181900360200190a15b50565b6000546001600160a01b031633146107be576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b6005548114156107cd57610770565b60058190556040805182815290517fa02ce31a8a8adcdc2e2811a0c7f5d1eb1aa920ca9fdfaeaebfe3a2163e69a6549181900360200190a150565b6001546001600160a01b0316331461085a576040805162461bcd60e51b815260206004820152601060248201526f37b7363ca832b73234b733a7bbb732b960811b604482015290519081900360640190fd5b600154600080546040516001600160a01b0393841693909116917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a360018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b6000546001600160a01b031681565b60065481565b6004546001600160a01b031681565b60075481565b6001546001600160a01b031681565b6002546001600160a01b031681565b6000546001600160a01b03163314610951576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b60065481141561096057610770565b60068190556040805182815290517fe08bf32e9c0e823a76d0088908afba678014c513e2311bba64fc72f38ae809709181900360200190a150565b6000546001600160a01b031633146109e6576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b60055481565b6008546001600160a01b031681565b600080610a2985610a89565b8051602091820120604080516001600160f81b0319818501526bffffffffffffffffffffffff19606089901b1660218201526035810187905260558082019390935281518082039093018352607501905280519101209150509392505050565b604080516057810190915260378152733d602d80600a3d3981f3363d3d373d3d3d363d7360601b602082015260609190911b60348201526e5af43d82803e903d91602b57fd5bf360881b604882015290565b825160009082816020870184f591506001600160a01b038216610b3c576040805162461bcd60e51b8152602060048201526014602482015273195c9c9bdc97d85b1c9958591e50dc99585d195960621b604482015290519081900360640190fd5b835115610c35576000826001600160a01b0316856040518082805190602001908083835b60208310610b7f5780518252601f199092019160209182019101610b60565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610be1576040519150601f19603f3d011682016040523d82523d6000602084013e610be6565b606091505b5050905080610c33576040805162461bcd60e51b815260206004820152601460248201527332b93937b92fb4b734ba34b0b634bd30ba34b7b760611b604482015290519081900360640190fd5b505b50939250505056fea2646970667358221220393a459d6b64473d1209ef5ca9a87312b5cca4b2065323b7ab4866081ede390164736f6c63430006060033", + "deployedBytecode": "0x6080604052600436106100f75760003560e01c8063c5a8c91f1161008a578063f0ef0b0611610059578063f0ef0b0614610322578063f2fde38b1461034c578063f7c1329e1461037f578063fc0c546a14610394576100fe565b8063c5a8c91f146102ce578063e22ab5ae146102e3578063e30c3978146102f8578063e4a154a41461030d576100fe565b80634e51a863116100c65780634e51a863146102535780634e71e0c81461027d5780638da5cb5b14610292578063afc6224b146102a7576100fe565b80631062b39a1461010357806317c2a98c14610134578063325ff66f1461016757806337dd8b0514610227576100fe565b366100fe57005b600080fd5b34801561010f57600080fd5b506101186103a9565b604080516001600160a01b039092168252519081900360200190f35b34801561014057600080fd5b506101186004803603602081101561015757600080fd5b50356001600160a01b03166103b8565b34801561017357600080fd5b506101186004803603604081101561018a57600080fd5b6001600160a01b0382351691908101906040810160208201356401000000008111156101b557600080fd5b8201836020820111156101c757600080fd5b803590602001918460208302840111640100000000831117156101e957600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506103dd945050505050565b34801561023357600080fd5b506102516004803603602081101561024a57600080fd5b50356106dd565b005b34801561025f57600080fd5b506102516004803603602081101561027657600080fd5b5035610773565b34801561028957600080fd5b50610251610808565b34801561029e57600080fd5b506101186108be565b3480156102b357600080fd5b506102bc6108cd565b60408051918252519081900360200190f35b3480156102da57600080fd5b506101186108d3565b3480156102ef57600080fd5b506102bc6108e2565b34801561030457600080fd5b506101186108e8565b34801561031957600080fd5b506101186108f7565b34801561032e57600080fd5b506102516004803603602081101561034557600080fd5b5035610906565b34801561035857600080fd5b506102516004803603602081101561036f57600080fd5b50356001600160a01b031661099b565b34801561038b57600080fd5b506102bc610a08565b3480156103a057600080fd5b50610118610a0e565b6003546001600160a01b031681565b6002546000906103d7906001600160a01b039081169030908516610a1d565b92915050565b6003546000906001600160a01b0316331461042a576040805162461bcd60e51b815260206004820152600860248201526737b7363cafa0a6a160c11b604482015290519081900360640190fd5b6003546040805163d67bdd2560e01b815290516000926001600160a01b03169163d67bdd25916004808301926020929190829003018186803b15801561046f57600080fd5b505afa158015610483573d6000803e3d6000fd5b505050506040513d602081101561049957600080fd5b50516008546004546007546040516001600160a01b0389811660248301908152948116604483018190529381166084830181905290861660a4830181905260c4830184905260c0606484019081528a5160e48501528a5197985090966060968c96958c958b949193610104909101906020888101910280838360005b8381101561052d578181015183820152602001610515565b50506040805193909501838103601f190184529094525060208101805163015c7f5160e01b6001600160e01b03909116179052600254909b5060009a50610592995061058b98506001600160a01b03169650610a8995505050505050565b8385610adb565b600254604080516001600160a01b0392831681529051929350818a1692828516928816917f90d0a5d098b9a181ff8ddc866f840cc210e5b91eaf27bc267d5822a0deafad25919081900360200190a4600554158015906105f457506005544710155b1561065a576005546040516001600160a01b0383169180156108fc02916000818181858888f193505050501561065a5760055460408051918252517f517165f169759cdb94227d1c50f4f47895eb099a7f04a780f519bf1739face6f9181900360200190a15b6006541580159061066d57506006544710155b156106d3576006546040516001600160a01b0389169180156108fc02916000818181858888f19350505050156106d35760065460408051918252517f69e30c0bf438d0d3e0afb7f68d57ef394a0d5e8712f82fa00aa599e42574bc2a9181900360200190a15b9695505050505050565b6000546001600160a01b03163314610728576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b60075481141561073757610770565b60078190556040805182815290517f7a78bdfbfb2e909f35c05c77e80038cfd0a22c704748eba8b1d20aab76cd5d9c9181900360200190a15b50565b6000546001600160a01b031633146107be576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b6005548114156107cd57610770565b60058190556040805182815290517fa02ce31a8a8adcdc2e2811a0c7f5d1eb1aa920ca9fdfaeaebfe3a2163e69a6549181900360200190a150565b6001546001600160a01b0316331461085a576040805162461bcd60e51b815260206004820152601060248201526f37b7363ca832b73234b733a7bbb732b960811b604482015290519081900360640190fd5b600154600080546040516001600160a01b0393841693909116917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a360018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b6000546001600160a01b031681565b60065481565b6004546001600160a01b031681565b60075481565b6001546001600160a01b031681565b6002546001600160a01b031681565b6000546001600160a01b03163314610951576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b60065481141561096057610770565b60068190556040805182815290517fe08bf32e9c0e823a76d0088908afba678014c513e2311bba64fc72f38ae809709181900360200190a150565b6000546001600160a01b031633146109e6576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b60055481565b6008546001600160a01b031681565b600080610a2985610a89565b8051602091820120604080516001600160f81b0319818501526bffffffffffffffffffffffff19606089901b1660218201526035810187905260558082019390935281518082039093018352607501905280519101209150509392505050565b604080516057810190915260378152733d602d80600a3d3981f3363d3d373d3d3d363d7360601b602082015260609190911b60348201526e5af43d82803e903d91602b57fd5bf360881b604882015290565b825160009082816020870184f591506001600160a01b038216610b3c576040805162461bcd60e51b8152602060048201526014602482015273195c9c9bdc97d85b1c9958591e50dc99585d195960621b604482015290519081900360640190fd5b835115610c35576000826001600160a01b0316856040518082805190602001908083835b60208310610b7f5780518252601f199092019160209182019101610b60565b6001836020036101000a0380198251168184511680821785525050505050509050019150506000604051808303816000865af19150503d8060008114610be1576040519150601f19603f3d011682016040523d82523d6000602084013e610be6565b606091505b5050905080610c33576040805162461bcd60e51b815260206004820152601460248201527332b93937b92fb4b734ba34b0b634bd30ba34b7b760611b604482015290519081900360640190fd5b505b50939250505056fea2646970667358221220393a459d6b64473d1209ef5ca9a87312b5cca4b2065323b7ab4866081ede390164736f6c63430006060033", + "immutableReferences": {}, + "sourceMap": "324:3380:2:-:0;;;1087:334;5:9:-1;2:2;;;27:1;24;17:12;2:2;1087:334:2;;;;;;;;;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;1087:334:2;;;;;;;;;;;;577:5:12;:14;;1195:10:2;-1:-1:-1;;;;;;577:14:12;;;;;;;1217:5:2::1;:14:::0;;;::::1;-1:-1:-1::0;;;;;1217:14:2;;::::1;::::0;;;::::1;::::0;;;1241::::1;:48:::0;;;::::1;::::0;;::::1;;::::0;;;1299:29:::1;:62:::0;;;;::::1;::::0;;::::1;;::::0;;;1382:31;;-1:-1:-1;;;1382:31:2;;;;1087:334;;;;;;1382:14;;;::::1;::::0;:29:::1;::::0;:31;;::::1;::::0;1087:334;1382:31;;;;;;;:14;:31;::::1;;2:2:-1::0;::::1;;;27:1;24::::0;17:12:::1;2:2;1382:31:2;;;;8:9:-1;5:2;;;45:16;42:1;39::::0;24:38:::1;77:16;74:1;67:27;5:2;1382:31:2;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28::::0;21:12:::1;4:2;-1:-1:::0;1382:31:2;1371:3:::1;:43:::0;;-1:-1:-1;;;;;1371:43:2;;::::1;-1:-1:-1::0;;;;;;1371:43:2;;::::1;::::0;;;::::1;::::0;;-1:-1:-1;;;324:3380:2;;;;;;", + "deployedSourceMap": "324:3380:2:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12:1:-1;9;2:12;792:15:2;;5:9:-1;2:2;;;27:1;24;17:12;2:2;792:15:2;;;:::i;:::-;;;;-1:-1:-1;;;;;792:15:2;;;;;;;;;;;;;;2081:245;;5:9:-1;2:2;;;27:1;24;17:12;2:2;2081:245:2;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;2081:245:2;-1:-1:-1;;;;;2081:245:2;;:::i;2431:1271::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;2431:1271:2;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;;;;;2431:1271:2;;;;;;;;;;;;;;;27:11:-1;11:28;;8:2;;;52:1;49;42:12;8:2;2431:1271:2;;41:9:-1;34:4;18:14;14:25;11:40;8:2;;;64:1;61;54:12;8:2;2431:1271:2;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;39:11;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;2431:1271:2;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;2431:1271:2;;-1:-1:-1;2431:1271:2;;-1:-1:-1;;;;;2431:1271:2:i;1874:200::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1874:200:2;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;1874:200:2;;:::i;:::-;;1488:177;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1488:177:2;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;1488:177:2;;:::i;1123:226:12:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1123:226:12;;;:::i;236:20::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;236:20:12;;;:::i;986:32:2:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;986:32:2;;;:::i;:::-;;;;;;;;;;;;;;;;813:36;;5:9:-1;2:2;;;27:1;24;17:12;2:2;813:36:2;;;:::i;1024:31::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1024:31:2;;;:::i;262:27:12:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;262:27:12;;;:::i;742:44:2:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;742:44:2;;;:::i;1671:197::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1671:197:2;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;1671:197:2;;:::i;929:102:12:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;929:102:12;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;929:102:12;-1:-1:-1;;;;;929:102:12;;:::i;953:27:2:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;953:27:2;;;:::i;1061:20::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1061:20:2;;;:::i;792:15::-;;;-1:-1:-1;;;;;792:15:2;;:::o;2081:245::-;2240:29;;2168:13;;2204:115;;-1:-1:-1;;;;;2240:29:2;;;;2279:4;;2294:23;;2204:35;:115::i;:::-;2197:122;2081:245;-1:-1:-1;;2081:245:2:o;2431:1271::-;2574:3;;2525:7;;-1:-1:-1;;;;;2574:3:2;2552:10;:26;2544:47;;;;;-1:-1:-1;;;2544:47:2;;;;;;;;;;;;-1:-1:-1;;;2544:47:2;;;;;;;;;;;;;;;2621:3;;:19;;;-1:-1:-1;;;2621:19:2;;;;2601:17;;-1:-1:-1;;;;;2621:3:2;;:17;;:19;;;;;;;;;;;;;;:3;:19;;;2:2:-1;;;;27:1;24;17:12;2:2;2621:19:2;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;2621:19:2;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;2621:19:2;2842:5;;2889:14;;2941:19;;2722:248;;-1:-1:-1;;;;;2722:248:2;;;;;;;;;2842:5;;;2722:248;;;;;;2889:14;;;2722:248;;;;;;2673:18;;;2722:248;;;;;;;;;;;;;;;;;;;;;;;;;;;2621:19;;-1:-1:-1;2673:18:2;;2702:17;;2823:5;;2842;2861:6;;2621:19;;2941;;2722:248;;;;;2621:19;2722:248;;;;;;;;2650:12;8:100:-1;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;-1:-1;;2722:248:2;;;;;;;26:21:-1;;;-1:-1;;22:32;6:49;;2722:248:2;;;-1:-1:-1;49:4;25:18;;61:17;;-1:-1;;;;;;;;182:15;;;179:29;160:49;;3063:29:2;;2722:248;;-1:-1:-1;;;;3001:105:2;;-1:-1:-1;3040:53:2;;-1:-1:-1;;;;;;3063:29:2;;-1:-1:-1;3040:22:2;;-1:-1:-1;;;;;;3040:53:2:i;:::-;3095:4;3101;3001:38;:105::i;:::-;3162:29;;3121:71;;;-1:-1:-1;;;;;3162:29:2;;;3121:71;;;;2980:126;;-1:-1:-1;3121:71:2;;;;;;;;;;;;;;;;;;;;;3256:15;;:20;;;;:64;;;3305:15;;3280:21;:40;;3256:64;3252:195;;;3348:15;;3340:24;;-1:-1:-1;;;;;3340:7:2;;;:24;;;;;;;;;3348:15;3340:7;:24;;;;;;;3336:101;;;3406:15;;3389:33;;;;;;;;;;;;;;;;3336:101;3460:20;;:25;;;;:74;;;3514:20;;3489:21;:45;;3460:74;3456:221;;;3565:20;;3554:32;;-1:-1:-1;;;;;3554:10:2;;;:32;;;;;;;;;3565:20;3554:10;:32;;;;;;;3550:117;;;3631:20;;3611:41;;;;;;;;;;;;;;;;3550:117;3693:2;2431:1271;-1:-1:-1;;;;;;2431:1271:2:o;1874:200::-;739:5:12;;-1:-1:-1;;;;;739:5:12;725:10;:19;717:41;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;;;;1953:19:2::1;;1946:3;:26;1943:38;;;1974:7;;1943:38;1990:19;:25:::0;;;2030:37:::1;::::0;;;;;;;::::1;::::0;;;;::::1;::::0;;::::1;768:1:12;1874:200:2::0;:::o;1488:177::-;739:5:12;;-1:-1:-1;;;;;739:5:12;725:10;:19;717:41;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;;;;1563:15:2::1;;1556:3;:22;1553:34;;;1580:7;;1553:34;1596:15;:21:::0;;;1632:26:::1;::::0;;;;;;;::::1;::::0;;;;::::1;::::0;;::::1;1488:177:::0;:::o;1123:226:12:-;1188:12;;-1:-1:-1;;;;;1188:12:12;1174:10;:26;1166:55;;;;;-1:-1:-1;;;1166:55:12;;;;;;;;;;;;-1:-1:-1;;;1166:55:12;;;;;;;;;;;;;;;1264:12;;;1257:5;;1236:41;;-1:-1:-1;;;;;1264:12:12;;;;1257:5;;;;1236:41;;;1295:12;;;;1287:20;;-1:-1:-1;;;;;;1287:20:12;;;-1:-1:-1;;;;;1295:12:12;;1287:20;;;;1317:25;;;1123:226::o;236:20::-;;;-1:-1:-1;;;;;236:20:12;;:::o;986:32:2:-;;;;:::o;813:36::-;;;-1:-1:-1;;;;;813:36:2;;:::o;1024:31::-;;;;:::o;262:27:12:-;;;-1:-1:-1;;;;;262:27:12;;:::o;742:44:2:-;;;-1:-1:-1;;;;;742:44:2;;:::o;1671:197::-;739:5:12;;-1:-1:-1;;;;;739:5:12;725:10;:19;717:41;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;;;;1751:20:2::1;;1744:3;:27;1741:39;;;1773:7;;1741:39;1789:20;:26:::0;;;1830:31:::1;::::0;;;;;;;::::1;::::0;;;;::::1;::::0;;::::1;1671:197:::0;:::o;929:102:12:-;739:5;;-1:-1:-1;;;;;739:5:12;725:10;:19;717:41;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;;;;1001:12:::1;:23:::0;;-1:-1:-1;;;;;;1001:23:12::1;-1:-1:-1::0;;;;;1001:23:12;;;::::1;::::0;;;::::1;::::0;;929:102::o;953:27:2:-;;;;:::o;1061:20::-;;;-1:-1:-1;;;;;1061:20:2;;:::o;1371:393:0:-;1510:13;1535:16;1564:23;1578:8;1564:13;:23::i;:::-;1554:34;;;;;;;1639:114;;;-1:-1:-1;;;;;;1639:114:0;;;;-1:-1:-1;;1639:114:0;;;;;;;;;;;;;;;;;;;;;;;;;26:21:-1;;;22:32;;;6:49;;1639:114:0;;;;1629:125;;;;;;-1:-1:-1;;1371:393:0;;;;;:::o;504:712::-;683:4;677:11;;724:4;714:15;;701:29;;;840:4;827:18;;-1:-1:-1;;;973:4:0;963:15;;956:91;568:17;619;;;;1077:4;1067:15;;1060:36;-1:-1:-1;;;1126:4:0;1116:15;;1109:91;677:11;655:555::o;2256:512::-;2446:11;;2399:21;;2532:4;2446:11;2520:4;2510:15;;2399:21;2499:38;2490:47;-1:-1:-1;;;;;;2564:19:0;;2556:52;;;;;-1:-1:-1;;;2556:52:0;;;;;;;;;;;;-1:-1:-1;;;2556:52:0;;;;;;;;;;;;;;;2622:15;;:20;2618:144;;2659:12;2677:5;-1:-1:-1;;;;;2677:10:0;2688:8;2677:20;;;;;;;;;;;;;36:153:-1;66:2;61:3;58:11;36:153;;176:10;;164:23;;-1:-1;;139:12;;;;98:2;89:12;;;;114;36:153;;;274:1;267:3;263:2;259:12;254:3;250:22;246:30;315:4;311:9;305:3;299:10;295:26;356:4;350:3;344:10;340:21;389:7;380;377:20;372:3;365:33;3:399;;;2677:20:0;;;;;;;;;;;;;;;;;;;;;;;;12:1:-1;19;14:27;;;;67:4;61:11;56:16;;134:4;130:9;123:4;105:16;101:27;97:43;94:1;90:51;84:4;77:65;157:16;154:1;147:27;211:16;208:1;201:4;198:1;194:12;179:49;5:228;;14:27;32:4;27:9;;5:228;;2658:39:0;;;2719:7;2711:40;;;;;-1:-1:-1;;;2711:40:0;;;;;;;;;;;;-1:-1:-1;;;2711:40:0;;;;;;;;;;;;;;;2618:144;;2256:512;;;;;;:::o", + "source": "pragma solidity 0.6.6;\n\nimport \"openzeppelin-solidity/contracts/token/ERC20/IERC20.sol\";\nimport \"openzeppelin-solidity/contracts/math/SafeMath.sol\";\nimport \"./CloneLib.sol\";\nimport \"./IAMB.sol\";\nimport \"./ITokenMediator.sol\";\nimport \"./Ownable.sol\"; // TODO: switch to \"openzeppelin-solidity/contracts/access/Ownable.sol\";\n\ncontract DataUnionFactorySidechain is Ownable{\n event SidechainDUCreated(address indexed mainnet, address indexed sidenet, address indexed owner, address template);\n event UpdateNewDUInitialEth(uint amount);\n event UpdateNewDUOwnerInitialEth(uint amount);\n event UpdateDefaultNewMemberInitialEth(uint amount);\n event DUInitialEthSent(uint amountWei);\n event OwnerInitialEthSent(uint amountWei);\n\n address public data_union_sidechain_template;\n IAMB public amb;\n ITokenMediator public token_mediator;\n \n // when sidechain DU is created, the factory sends a bit of sETH to the DU and the owner\n uint public newDUInitialEth;\n uint public newDUOwnerInitialEth;\n uint public defaultNewMemberEth;\n address public token;\n constructor(address _token, address _token_mediator, address _data_union_sidechain_template) public Ownable(msg.sender) {\n token = _token;\n token_mediator = ITokenMediator(_token_mediator);\n data_union_sidechain_template = _data_union_sidechain_template;\n amb = IAMB(token_mediator.bridgeContract());\n }\n\n //contract is payable\n receive() external payable {}\n\n function setNewDUInitialEth(uint val) public onlyOwner {\n if(val == newDUInitialEth) return;\n newDUInitialEth = val;\n emit UpdateNewDUInitialEth(val);\n }\n\n function setNewDUOwnerInitialEth(uint val) public onlyOwner {\n if(val == newDUOwnerInitialEth) return;\n newDUOwnerInitialEth = val;\n emit UpdateNewDUOwnerInitialEth(val);\n }\n\n function setNewMemberInitialEth(uint val) public onlyOwner {\n if(val == defaultNewMemberEth) return;\n defaultNewMemberEth = val;\n emit UpdateDefaultNewMemberInitialEth(val);\n }\n\n\n function sidechainAddress(address mainet_address)\n public view\n returns (address proxy)\n {\n return CloneLib.predictCloneAddressCreate2(data_union_sidechain_template, address(this), bytes32(uint256(mainet_address)));\n }\n\n /*\n Must be called by AMB. Use MockAMB for testing.\n salt = mainnet_address.\n */\n \n function deployNewDUSidechain(address payable owner, address[] memory agents) public returns (address) {\n require(msg.sender == address(amb), \"only_AMB\");\n address duMainnet = amb.messageSender();\n bytes32 salt = bytes32(uint256(duMainnet));\n bytes memory data = abi.encodeWithSignature(\"initialize(address,address,address[],address,address,uint256)\",\n owner,\n token,\n agents,\n address(token_mediator),\n duMainnet,\n defaultNewMemberEth\n );\n address payable du = CloneLib.deployCodeAndInitUsingCreate2(CloneLib.cloneBytecode(data_union_sidechain_template), data, salt);\n emit SidechainDUCreated(duMainnet, du, owner, data_union_sidechain_template);\n\n // continue whether or not send succeeds\n if (newDUInitialEth != 0 && address(this).balance >= newDUInitialEth) {\n if (du.send(newDUInitialEth)) {\n emit DUInitialEthSent(newDUInitialEth);\n }\n }\n if (newDUOwnerInitialEth != 0 && address(this).balance >= newDUOwnerInitialEth) {\n if (owner.send(newDUOwnerInitialEth)) {\n emit OwnerInitialEthSent(newDUOwnerInitialEth);\n }\n }\n return du;\n }\n}\n", + "sourcePath": "/home/heynow/streamr/data-union-solidity/contracts/DataUnionFactorySidechain.sol", + "ast": { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/DataUnionFactorySidechain.sol", + "exportedSymbols": { + "DataUnionFactorySidechain": [ + 666 + ] + }, + "id": 667, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 365, + "literals": [ + "solidity", + "0.6", + ".6" + ], + "nodeType": "PragmaDirective", + "src": "0:22:2" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol", + "file": "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol", + "id": 366, + "nodeType": "ImportDirective", + "scope": 667, + "sourceUnit": 4283, + "src": "24:64:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "file": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "id": 367, + "nodeType": "ImportDirective", + "scope": 667, + "sourceUnit": 3642, + "src": "89:59:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/CloneLib.sol", + "file": "./CloneLib.sol", + "id": 368, + "nodeType": "ImportDirective", + "scope": 667, + "sourceUnit": 155, + "src": "149:24:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/IAMB.sol", + "file": "./IAMB.sol", + "id": 369, + "nodeType": "ImportDirective", + "scope": 667, + "sourceUnit": 2913, + "src": "174:20:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/ITokenMediator.sol", + "file": "./ITokenMediator.sol", + "id": 370, + "nodeType": "ImportDirective", + "scope": 667, + "sourceUnit": 2969, + "src": "195:30:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/Ownable.sol", + "file": "./Ownable.sol", + "id": 371, + "nodeType": "ImportDirective", + "scope": 667, + "sourceUnit": 3181, + "src": "226:23:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "abstract": false, + "baseContracts": [ + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 372, + "name": "Ownable", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 3180, + "src": "362:7:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_Ownable_$3180", + "typeString": "contract Ownable" + } + }, + "id": 373, + "nodeType": "InheritanceSpecifier", + "src": "362:7:2" + } + ], + "contractDependencies": [ + 3180 + ], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 666, + "linearizedBaseContracts": [ + 666, + 3180 + ], + "name": "DataUnionFactorySidechain", + "nodeType": "ContractDefinition", + "nodes": [ + { + "anonymous": false, + "documentation": null, + "id": 383, + "name": "SidechainDUCreated", + "nodeType": "EventDefinition", + "parameters": { + "id": 382, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 375, + "indexed": true, + "mutability": "mutable", + "name": "mainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 383, + "src": "400:23:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 374, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "400:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 377, + "indexed": true, + "mutability": "mutable", + "name": "sidenet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 383, + "src": "425:23:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 376, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "425:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 379, + "indexed": true, + "mutability": "mutable", + "name": "owner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 383, + "src": "450:21:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 378, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "450:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 381, + "indexed": false, + "mutability": "mutable", + "name": "template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 383, + "src": "473:16:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 380, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "473:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "399:91:2" + }, + "src": "375:116:2" + }, + { + "anonymous": false, + "documentation": null, + "id": 387, + "name": "UpdateNewDUInitialEth", + "nodeType": "EventDefinition", + "parameters": { + "id": 386, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 385, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 387, + "src": "524:11:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 384, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "524:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "523:13:2" + }, + "src": "496:41:2" + }, + { + "anonymous": false, + "documentation": null, + "id": 391, + "name": "UpdateNewDUOwnerInitialEth", + "nodeType": "EventDefinition", + "parameters": { + "id": 390, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 389, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 391, + "src": "575:11:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 388, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "575:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "574:13:2" + }, + "src": "542:46:2" + }, + { + "anonymous": false, + "documentation": null, + "id": 395, + "name": "UpdateDefaultNewMemberInitialEth", + "nodeType": "EventDefinition", + "parameters": { + "id": 394, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 393, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 395, + "src": "632:11:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 392, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "632:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "631:13:2" + }, + "src": "593:52:2" + }, + { + "anonymous": false, + "documentation": null, + "id": 399, + "name": "DUInitialEthSent", + "nodeType": "EventDefinition", + "parameters": { + "id": 398, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 397, + "indexed": false, + "mutability": "mutable", + "name": "amountWei", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 399, + "src": "673:14:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 396, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "673:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "672:16:2" + }, + "src": "650:39:2" + }, + { + "anonymous": false, + "documentation": null, + "id": 403, + "name": "OwnerInitialEthSent", + "nodeType": "EventDefinition", + "parameters": { + "id": 402, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 401, + "indexed": false, + "mutability": "mutable", + "name": "amountWei", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 403, + "src": "720:14:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 400, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "720:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "719:16:2" + }, + "src": "694:42:2" + }, + { + "constant": false, + "functionSelector": "e4a154a4", + "id": 405, + "mutability": "mutable", + "name": "data_union_sidechain_template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 666, + "src": "742:44:2", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 404, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "742:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "1062b39a", + "id": 407, + "mutability": "mutable", + "name": "amb", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 666, + "src": "792:15:2", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + }, + "typeName": { + "contractScope": null, + "id": 406, + "name": "IAMB", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 2912, + "src": "792:4:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "c5a8c91f", + "id": 409, + "mutability": "mutable", + "name": "token_mediator", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 666, + "src": "813:36:2", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + }, + "typeName": { + "contractScope": null, + "id": 408, + "name": "ITokenMediator", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 2968, + "src": "813:14:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "f7c1329e", + "id": 411, + "mutability": "mutable", + "name": "newDUInitialEth", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 666, + "src": "953:27:2", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 410, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "953:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "afc6224b", + "id": 413, + "mutability": "mutable", + "name": "newDUOwnerInitialEth", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 666, + "src": "986:32:2", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 412, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "986:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "e22ab5ae", + "id": 415, + "mutability": "mutable", + "name": "defaultNewMemberEth", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 666, + "src": "1024:31:2", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 414, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1024:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "fc0c546a", + "id": 417, + "mutability": "mutable", + "name": "token", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 666, + "src": "1061:20:2", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 416, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1061:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "body": { + "id": 452, + "nodeType": "Block", + "src": "1207:214:2", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 432, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 430, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 417, + "src": "1217:5:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 431, + "name": "_token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 419, + "src": "1225:6:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "1217:14:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 433, + "nodeType": "ExpressionStatement", + "src": "1217:14:2" + }, + { + "expression": { + "argumentTypes": null, + "id": 438, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 434, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 409, + "src": "1241:14:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 436, + "name": "_token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 421, + "src": "1273:15:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 435, + "name": "ITokenMediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2968, + "src": "1258:14:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_ITokenMediator_$2968_$", + "typeString": "type(contract ITokenMediator)" + } + }, + "id": 437, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1258:31:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "src": "1241:48:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 439, + "nodeType": "ExpressionStatement", + "src": "1241:48:2" + }, + { + "expression": { + "argumentTypes": null, + "id": 442, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 440, + "name": "data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 405, + "src": "1299:29:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 441, + "name": "_data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 423, + "src": "1331:30:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "1299:62:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 443, + "nodeType": "ExpressionStatement", + "src": "1299:62:2" + }, + { + "expression": { + "argumentTypes": null, + "id": 450, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 444, + "name": "amb", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 407, + "src": "1371:3:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "expression": { + "argumentTypes": null, + "id": 446, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 409, + "src": "1382:14:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 447, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "bridgeContract", + "nodeType": "MemberAccess", + "referencedDeclaration": 2946, + "src": "1382:29:2", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$__$returns$_t_address_$", + "typeString": "function () view external returns (address)" + } + }, + "id": 448, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1382:31:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 445, + "name": "IAMB", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2912, + "src": "1377:4:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_IAMB_$2912_$", + "typeString": "type(contract IAMB)" + } + }, + "id": 449, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1377:37:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "src": "1371:43:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "id": 451, + "nodeType": "ExpressionStatement", + "src": "1371:43:2" + } + ] + }, + "documentation": null, + "id": 453, + "implemented": true, + "kind": "constructor", + "modifiers": [ + { + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 426, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "1195:3:2", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 427, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "1195:10:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "id": 428, + "modifierName": { + "argumentTypes": null, + "id": 425, + "name": "Ownable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3180, + "src": "1187:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_Ownable_$3180_$", + "typeString": "type(contract Ownable)" + } + }, + "nodeType": "ModifierInvocation", + "src": "1187:19:2" + } + ], + "name": "", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 424, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 419, + "mutability": "mutable", + "name": "_token", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 453, + "src": "1099:14:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 418, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1099:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 421, + "mutability": "mutable", + "name": "_token_mediator", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 453, + "src": "1115:23:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 420, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1115:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 423, + "mutability": "mutable", + "name": "_data_union_sidechain_template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 453, + "src": "1140:38:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 422, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1140:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1098:81:2" + }, + "returnParameters": { + "id": 429, + "nodeType": "ParameterList", + "parameters": [], + "src": "1207:0:2" + }, + "scope": 666, + "src": "1087:334:2", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 456, + "nodeType": "Block", + "src": "1480:2:2", + "statements": [] + }, + "documentation": null, + "id": 457, + "implemented": true, + "kind": "receive", + "modifiers": [], + "name": "", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 454, + "nodeType": "ParameterList", + "parameters": [], + "src": "1460:2:2" + }, + "returnParameters": { + "id": 455, + "nodeType": "ParameterList", + "parameters": [], + "src": "1480:0:2" + }, + "scope": 666, + "src": "1453:29:2", + "stateMutability": "payable", + "virtual": false, + "visibility": "external" + }, + { + "body": { + "id": 477, + "nodeType": "Block", + "src": "1543:122:2", + "statements": [ + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 466, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 464, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 459, + "src": "1556:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 465, + "name": "newDUInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 411, + "src": "1563:15:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1556:22:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 468, + "nodeType": "IfStatement", + "src": "1553:34:2", + "trueBody": { + "expression": null, + "functionReturnParameters": 463, + "id": 467, + "nodeType": "Return", + "src": "1580:7:2" + } + }, + { + "expression": { + "argumentTypes": null, + "id": 471, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 469, + "name": "newDUInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 411, + "src": "1596:15:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 470, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 459, + "src": "1614:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1596:21:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 472, + "nodeType": "ExpressionStatement", + "src": "1596:21:2" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 474, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 459, + "src": "1654:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 473, + "name": "UpdateNewDUInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 387, + "src": "1632:21:2", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 475, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1632:26:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 476, + "nodeType": "EmitStatement", + "src": "1627:31:2" + } + ] + }, + "documentation": null, + "functionSelector": "4e51a863", + "id": 478, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 462, + "modifierName": { + "argumentTypes": null, + "id": 461, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "1533:9:2", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "1533:9:2" + } + ], + "name": "setNewDUInitialEth", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 460, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 459, + "mutability": "mutable", + "name": "val", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 478, + "src": "1516:8:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 458, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1516:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1515:10:2" + }, + "returnParameters": { + "id": 463, + "nodeType": "ParameterList", + "parameters": [], + "src": "1543:0:2" + }, + "scope": 666, + "src": "1488:177:2", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 498, + "nodeType": "Block", + "src": "1731:137:2", + "statements": [ + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 487, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 485, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 480, + "src": "1744:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 486, + "name": "newDUOwnerInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 413, + "src": "1751:20:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1744:27:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 489, + "nodeType": "IfStatement", + "src": "1741:39:2", + "trueBody": { + "expression": null, + "functionReturnParameters": 484, + "id": 488, + "nodeType": "Return", + "src": "1773:7:2" + } + }, + { + "expression": { + "argumentTypes": null, + "id": 492, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 490, + "name": "newDUOwnerInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 413, + "src": "1789:20:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 491, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 480, + "src": "1812:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1789:26:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 493, + "nodeType": "ExpressionStatement", + "src": "1789:26:2" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 495, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 480, + "src": "1857:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 494, + "name": "UpdateNewDUOwnerInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 391, + "src": "1830:26:2", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 496, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1830:31:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 497, + "nodeType": "EmitStatement", + "src": "1825:36:2" + } + ] + }, + "documentation": null, + "functionSelector": "f0ef0b06", + "id": 499, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 483, + "modifierName": { + "argumentTypes": null, + "id": 482, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "1721:9:2", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "1721:9:2" + } + ], + "name": "setNewDUOwnerInitialEth", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 481, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 480, + "mutability": "mutable", + "name": "val", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 499, + "src": "1704:8:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 479, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1704:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1703:10:2" + }, + "returnParameters": { + "id": 484, + "nodeType": "ParameterList", + "parameters": [], + "src": "1731:0:2" + }, + "scope": 666, + "src": "1671:197:2", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 519, + "nodeType": "Block", + "src": "1933:141:2", + "statements": [ + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 508, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 506, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 501, + "src": "1946:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 507, + "name": "defaultNewMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 415, + "src": "1953:19:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1946:26:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 510, + "nodeType": "IfStatement", + "src": "1943:38:2", + "trueBody": { + "expression": null, + "functionReturnParameters": 505, + "id": 509, + "nodeType": "Return", + "src": "1974:7:2" + } + }, + { + "expression": { + "argumentTypes": null, + "id": 513, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 511, + "name": "defaultNewMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 415, + "src": "1990:19:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 512, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 501, + "src": "2012:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1990:25:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 514, + "nodeType": "ExpressionStatement", + "src": "1990:25:2" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 516, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 501, + "src": "2063:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 515, + "name": "UpdateDefaultNewMemberInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 395, + "src": "2030:32:2", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 517, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2030:37:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 518, + "nodeType": "EmitStatement", + "src": "2025:42:2" + } + ] + }, + "documentation": null, + "functionSelector": "37dd8b05", + "id": 520, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 504, + "modifierName": { + "argumentTypes": null, + "id": 503, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "1923:9:2", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "1923:9:2" + } + ], + "name": "setNewMemberInitialEth", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 502, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 501, + "mutability": "mutable", + "name": "val", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 520, + "src": "1906:8:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 500, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1906:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1905:10:2" + }, + "returnParameters": { + "id": 505, + "nodeType": "ParameterList", + "parameters": [], + "src": "1933:0:2" + }, + "scope": 666, + "src": "1874:200:2", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 543, + "nodeType": "Block", + "src": "2187:139:2", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 529, + "name": "data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 405, + "src": "2240:29:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 532, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "2279:4:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionFactorySidechain_$666", + "typeString": "contract DataUnionFactorySidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionFactorySidechain_$666", + "typeString": "contract DataUnionFactorySidechain" + } + ], + "id": 531, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2271:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 530, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2271:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 533, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2271:13:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 538, + "name": "mainet_address", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 522, + "src": "2302:14:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 537, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2294:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_uint256_$", + "typeString": "type(uint256)" + }, + "typeName": { + "id": 536, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "2294:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 539, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2294:23:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 535, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2286:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes32_$", + "typeString": "type(bytes32)" + }, + "typeName": { + "id": 534, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "2286:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 540, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2286:32:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "expression": { + "argumentTypes": null, + "id": 527, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "2204:8:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 528, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "predictCloneAddressCreate2", + "nodeType": "MemberAccess", + "referencedDeclaration": 61, + "src": "2204:35:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_address_$_t_address_$_t_bytes32_$returns$_t_address_$", + "typeString": "function (address,address,bytes32) pure returns (address)" + } + }, + "id": 541, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2204:115:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "functionReturnParameters": 526, + "id": 542, + "nodeType": "Return", + "src": "2197:122:2" + } + ] + }, + "documentation": null, + "functionSelector": "17c2a98c", + "id": 544, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "sidechainAddress", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 523, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 522, + "mutability": "mutable", + "name": "mainet_address", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 544, + "src": "2107:22:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 521, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2107:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2106:24:2" + }, + "returnParameters": { + "id": 526, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 525, + "mutability": "mutable", + "name": "proxy", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 544, + "src": "2168:13:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 524, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2168:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2167:15:2" + }, + "scope": 666, + "src": "2081:245:2", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 664, + "nodeType": "Block", + "src": "2534:1168:2", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 561, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 555, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "2552:3:2", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 556, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "2552:10:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 559, + "name": "amb", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 407, + "src": "2574:3:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + ], + "id": 558, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2566:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 557, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2566:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 560, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2566:12:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "2552:26:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6f6e6c795f414d42", + "id": 562, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2580:10:2", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_7698dac7a74e78fd5d71d9ff5c3f048a6bfa330cbd49f847a9726ba9ad094965", + "typeString": "literal_string \"only_AMB\"" + }, + "value": "only_AMB" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_7698dac7a74e78fd5d71d9ff5c3f048a6bfa330cbd49f847a9726ba9ad094965", + "typeString": "literal_string \"only_AMB\"" + } + ], + "id": 554, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "2544:7:2", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 563, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2544:47:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 564, + "nodeType": "ExpressionStatement", + "src": "2544:47:2" + }, + { + "assignments": [ + 566 + ], + "declarations": [ + { + "constant": false, + "id": 566, + "mutability": "mutable", + "name": "duMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 664, + "src": "2601:17:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 565, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2601:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 570, + "initialValue": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "expression": { + "argumentTypes": null, + "id": 567, + "name": "amb", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 407, + "src": "2621:3:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "id": 568, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "messageSender", + "nodeType": "MemberAccess", + "referencedDeclaration": 2852, + "src": "2621:17:2", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$__$returns$_t_address_$", + "typeString": "function () view external returns (address)" + } + }, + "id": 569, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2621:19:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "2601:39:2" + }, + { + "assignments": [ + 572 + ], + "declarations": [ + { + "constant": false, + "id": 572, + "mutability": "mutable", + "name": "salt", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 664, + "src": "2650:12:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 571, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "2650:7:2", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 580, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 577, + "name": "duMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 566, + "src": "2681:9:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 576, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2673:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_uint256_$", + "typeString": "type(uint256)" + }, + "typeName": { + "id": 575, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "2673:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 578, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2673:18:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 574, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2665:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes32_$", + "typeString": "type(bytes32)" + }, + "typeName": { + "id": 573, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "2665:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 579, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2665:27:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "2650:42:2" + }, + { + "assignments": [ + 582 + ], + "declarations": [ + { + "constant": false, + "id": 582, + "mutability": "mutable", + "name": "data", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 664, + "src": "2702:17:2", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 581, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "2702:5:2", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 596, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "696e697469616c697a6528616464726573732c616464726573732c616464726573735b5d2c616464726573732c616464726573732c75696e7432353629", + "id": 585, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2746:63:2", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_015c7f51e2589abfef5a1063cf7321fa0da4fb1756a7b671e8b1ca6858d2b75f", + "typeString": "literal_string \"initialize(address,address,address[],address,address,uint256)\"" + }, + "value": "initialize(address,address,address[],address,address,uint256)" + }, + { + "argumentTypes": null, + "id": 586, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 546, + "src": "2823:5:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 587, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 417, + "src": "2842:5:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 588, + "name": "agents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 549, + "src": "2861:6:2", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 591, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 409, + "src": "2889:14:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + ], + "id": 590, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2881:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 589, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2881:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 592, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2881:23:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 593, + "name": "duMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 566, + "src": "2918:9:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 594, + "name": "defaultNewMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 415, + "src": "2941:19:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_015c7f51e2589abfef5a1063cf7321fa0da4fb1756a7b671e8b1ca6858d2b75f", + "typeString": "literal_string \"initialize(address,address,address[],address,address,uint256)\"" + }, + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 583, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "2722:3:2", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 584, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encodeWithSignature", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "2722:23:2", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencodewithsignature_pure$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (string memory) pure returns (bytes memory)" + } + }, + "id": 595, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2722:248:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "2702:268:2" + }, + { + "assignments": [ + 598 + ], + "declarations": [ + { + "constant": false, + "id": 598, + "mutability": "mutable", + "name": "du", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 664, + "src": "2980:18:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + "typeName": { + "id": 597, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2980:15:2", + "stateMutability": "payable", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 608, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 603, + "name": "data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 405, + "src": "3063:29:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "expression": { + "argumentTypes": null, + "id": 601, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "3040:8:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 602, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "cloneBytecode", + "nodeType": "MemberAccess", + "referencedDeclaration": 18, + "src": "3040:22:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_address_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (address) pure returns (bytes memory)" + } + }, + "id": 604, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3040:53:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "id": 605, + "name": "data", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 582, + "src": "3095:4:2", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "id": 606, + "name": "salt", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 572, + "src": "3101:4:2", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "expression": { + "argumentTypes": null, + "id": 599, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "3001:8:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 600, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "deployCodeAndInitUsingCreate2", + "nodeType": "MemberAccess", + "referencedDeclaration": 108, + "src": "3001:38:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_bytes_memory_ptr_$_t_bytes_memory_ptr_$_t_bytes32_$returns$_t_address_payable_$", + "typeString": "function (bytes memory,bytes memory,bytes32) returns (address payable)" + } + }, + "id": 607, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3001:105:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "2980:126:2" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 610, + "name": "duMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 566, + "src": "3140:9:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 611, + "name": "du", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 598, + "src": "3151:2:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 612, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 546, + "src": "3155:5:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 613, + "name": "data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 405, + "src": "3162:29:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 609, + "name": "SidechainDUCreated", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 383, + "src": "3121:18:2", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_address_$_t_address_$_t_address_$returns$__$", + "typeString": "function (address,address,address,address)" + } + }, + "id": 614, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3121:71:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 615, + "nodeType": "EmitStatement", + "src": "3116:76:2" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 626, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 618, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 616, + "name": "newDUInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 411, + "src": "3256:15:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 617, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3275:1:2", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "3256:20:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "&&", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 625, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 621, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "3288:4:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionFactorySidechain_$666", + "typeString": "contract DataUnionFactorySidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionFactorySidechain_$666", + "typeString": "contract DataUnionFactorySidechain" + } + ], + "id": 620, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "3280:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 619, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3280:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 622, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3280:13:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "id": 623, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "balance", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "3280:21:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">=", + "rightExpression": { + "argumentTypes": null, + "id": 624, + "name": "newDUInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 411, + "src": "3305:15:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "3280:40:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "3256:64:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 638, + "nodeType": "IfStatement", + "src": "3252:195:2", + "trueBody": { + "id": 637, + "nodeType": "Block", + "src": "3322:125:2", + "statements": [ + { + "condition": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 629, + "name": "newDUInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 411, + "src": "3348:15:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 627, + "name": "du", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 598, + "src": "3340:2:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "id": 628, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "send", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "3340:7:2", + "typeDescriptions": { + "typeIdentifier": "t_function_send_nonpayable$_t_uint256_$returns$_t_bool_$", + "typeString": "function (uint256) returns (bool)" + } + }, + "id": 630, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3340:24:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 636, + "nodeType": "IfStatement", + "src": "3336:101:2", + "trueBody": { + "id": 635, + "nodeType": "Block", + "src": "3366:71:2", + "statements": [ + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 632, + "name": "newDUInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 411, + "src": "3406:15:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 631, + "name": "DUInitialEthSent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 399, + "src": "3389:16:2", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 633, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3389:33:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 634, + "nodeType": "EmitStatement", + "src": "3384:38:2" + } + ] + } + } + ] + } + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 649, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 641, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 639, + "name": "newDUOwnerInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 413, + "src": "3460:20:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 640, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3484:1:2", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "3460:25:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "&&", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 648, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 644, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "3497:4:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionFactorySidechain_$666", + "typeString": "contract DataUnionFactorySidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionFactorySidechain_$666", + "typeString": "contract DataUnionFactorySidechain" + } + ], + "id": 643, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "3489:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 642, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3489:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 645, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3489:13:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "id": 646, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "balance", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "3489:21:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">=", + "rightExpression": { + "argumentTypes": null, + "id": 647, + "name": "newDUOwnerInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 413, + "src": "3514:20:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "3489:45:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "3460:74:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 661, + "nodeType": "IfStatement", + "src": "3456:221:2", + "trueBody": { + "id": 660, + "nodeType": "Block", + "src": "3536:141:2", + "statements": [ + { + "condition": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 652, + "name": "newDUOwnerInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 413, + "src": "3565:20:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 650, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 546, + "src": "3554:5:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "id": 651, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "send", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "3554:10:2", + "typeDescriptions": { + "typeIdentifier": "t_function_send_nonpayable$_t_uint256_$returns$_t_bool_$", + "typeString": "function (uint256) returns (bool)" + } + }, + "id": 653, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3554:32:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 659, + "nodeType": "IfStatement", + "src": "3550:117:2", + "trueBody": { + "id": 658, + "nodeType": "Block", + "src": "3588:79:2", + "statements": [ + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 655, + "name": "newDUOwnerInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 413, + "src": "3631:20:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 654, + "name": "OwnerInitialEthSent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 403, + "src": "3611:19:2", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 656, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3611:41:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 657, + "nodeType": "EmitStatement", + "src": "3606:46:2" + } + ] + } + } + ] + } + }, + { + "expression": { + "argumentTypes": null, + "id": 662, + "name": "du", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 598, + "src": "3693:2:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "functionReturnParameters": 553, + "id": 663, + "nodeType": "Return", + "src": "3686:9:2" + } + ] + }, + "documentation": null, + "functionSelector": "325ff66f", + "id": 665, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "deployNewDUSidechain", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 550, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 546, + "mutability": "mutable", + "name": "owner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 665, + "src": "2461:21:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + "typeName": { + "id": 545, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2461:15:2", + "stateMutability": "payable", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 549, + "mutability": "mutable", + "name": "agents", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 665, + "src": "2484:23:2", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 547, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2484:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 548, + "length": null, + "nodeType": "ArrayTypeName", + "src": "2484:9:2", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2460:48:2" + }, + "returnParameters": { + "id": 553, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 552, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 665, + "src": "2525:7:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 551, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2525:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2524:9:2" + }, + "scope": 666, + "src": "2431:1271:2", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + } + ], + "scope": 667, + "src": "324:3380:2" + } + ], + "src": "0:3705:2" + }, + "legacyAST": { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/DataUnionFactorySidechain.sol", + "exportedSymbols": { + "DataUnionFactorySidechain": [ + 666 + ] + }, + "id": 667, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 365, + "literals": [ + "solidity", + "0.6", + ".6" + ], + "nodeType": "PragmaDirective", + "src": "0:22:2" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol", + "file": "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol", + "id": 366, + "nodeType": "ImportDirective", + "scope": 667, + "sourceUnit": 4283, + "src": "24:64:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "file": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "id": 367, + "nodeType": "ImportDirective", + "scope": 667, + "sourceUnit": 3642, + "src": "89:59:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/CloneLib.sol", + "file": "./CloneLib.sol", + "id": 368, + "nodeType": "ImportDirective", + "scope": 667, + "sourceUnit": 155, + "src": "149:24:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/IAMB.sol", + "file": "./IAMB.sol", + "id": 369, + "nodeType": "ImportDirective", + "scope": 667, + "sourceUnit": 2913, + "src": "174:20:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/ITokenMediator.sol", + "file": "./ITokenMediator.sol", + "id": 370, + "nodeType": "ImportDirective", + "scope": 667, + "sourceUnit": 2969, + "src": "195:30:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/Ownable.sol", + "file": "./Ownable.sol", + "id": 371, + "nodeType": "ImportDirective", + "scope": 667, + "sourceUnit": 3181, + "src": "226:23:2", + "symbolAliases": [], + "unitAlias": "" + }, + { + "abstract": false, + "baseContracts": [ + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 372, + "name": "Ownable", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 3180, + "src": "362:7:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_Ownable_$3180", + "typeString": "contract Ownable" + } + }, + "id": 373, + "nodeType": "InheritanceSpecifier", + "src": "362:7:2" + } + ], + "contractDependencies": [ + 3180 + ], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 666, + "linearizedBaseContracts": [ + 666, + 3180 + ], + "name": "DataUnionFactorySidechain", + "nodeType": "ContractDefinition", + "nodes": [ + { + "anonymous": false, + "documentation": null, + "id": 383, + "name": "SidechainDUCreated", + "nodeType": "EventDefinition", + "parameters": { + "id": 382, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 375, + "indexed": true, + "mutability": "mutable", + "name": "mainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 383, + "src": "400:23:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 374, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "400:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 377, + "indexed": true, + "mutability": "mutable", + "name": "sidenet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 383, + "src": "425:23:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 376, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "425:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 379, + "indexed": true, + "mutability": "mutable", + "name": "owner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 383, + "src": "450:21:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 378, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "450:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 381, + "indexed": false, + "mutability": "mutable", + "name": "template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 383, + "src": "473:16:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 380, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "473:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "399:91:2" + }, + "src": "375:116:2" + }, + { + "anonymous": false, + "documentation": null, + "id": 387, + "name": "UpdateNewDUInitialEth", + "nodeType": "EventDefinition", + "parameters": { + "id": 386, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 385, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 387, + "src": "524:11:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 384, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "524:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "523:13:2" + }, + "src": "496:41:2" + }, + { + "anonymous": false, + "documentation": null, + "id": 391, + "name": "UpdateNewDUOwnerInitialEth", + "nodeType": "EventDefinition", + "parameters": { + "id": 390, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 389, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 391, + "src": "575:11:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 388, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "575:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "574:13:2" + }, + "src": "542:46:2" + }, + { + "anonymous": false, + "documentation": null, + "id": 395, + "name": "UpdateDefaultNewMemberInitialEth", + "nodeType": "EventDefinition", + "parameters": { + "id": 394, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 393, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 395, + "src": "632:11:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 392, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "632:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "631:13:2" + }, + "src": "593:52:2" + }, + { + "anonymous": false, + "documentation": null, + "id": 399, + "name": "DUInitialEthSent", + "nodeType": "EventDefinition", + "parameters": { + "id": 398, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 397, + "indexed": false, + "mutability": "mutable", + "name": "amountWei", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 399, + "src": "673:14:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 396, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "673:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "672:16:2" + }, + "src": "650:39:2" + }, + { + "anonymous": false, + "documentation": null, + "id": 403, + "name": "OwnerInitialEthSent", + "nodeType": "EventDefinition", + "parameters": { + "id": 402, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 401, + "indexed": false, + "mutability": "mutable", + "name": "amountWei", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 403, + "src": "720:14:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 400, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "720:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "719:16:2" + }, + "src": "694:42:2" + }, + { + "constant": false, + "functionSelector": "e4a154a4", + "id": 405, + "mutability": "mutable", + "name": "data_union_sidechain_template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 666, + "src": "742:44:2", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 404, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "742:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "1062b39a", + "id": 407, + "mutability": "mutable", + "name": "amb", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 666, + "src": "792:15:2", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + }, + "typeName": { + "contractScope": null, + "id": 406, + "name": "IAMB", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 2912, + "src": "792:4:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "c5a8c91f", + "id": 409, + "mutability": "mutable", + "name": "token_mediator", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 666, + "src": "813:36:2", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + }, + "typeName": { + "contractScope": null, + "id": 408, + "name": "ITokenMediator", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 2968, + "src": "813:14:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "f7c1329e", + "id": 411, + "mutability": "mutable", + "name": "newDUInitialEth", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 666, + "src": "953:27:2", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 410, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "953:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "afc6224b", + "id": 413, + "mutability": "mutable", + "name": "newDUOwnerInitialEth", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 666, + "src": "986:32:2", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 412, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "986:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "e22ab5ae", + "id": 415, + "mutability": "mutable", + "name": "defaultNewMemberEth", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 666, + "src": "1024:31:2", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 414, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1024:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "fc0c546a", + "id": 417, + "mutability": "mutable", + "name": "token", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 666, + "src": "1061:20:2", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 416, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1061:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "body": { + "id": 452, + "nodeType": "Block", + "src": "1207:214:2", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 432, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 430, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 417, + "src": "1217:5:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 431, + "name": "_token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 419, + "src": "1225:6:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "1217:14:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 433, + "nodeType": "ExpressionStatement", + "src": "1217:14:2" + }, + { + "expression": { + "argumentTypes": null, + "id": 438, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 434, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 409, + "src": "1241:14:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 436, + "name": "_token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 421, + "src": "1273:15:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 435, + "name": "ITokenMediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2968, + "src": "1258:14:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_ITokenMediator_$2968_$", + "typeString": "type(contract ITokenMediator)" + } + }, + "id": 437, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1258:31:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "src": "1241:48:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 439, + "nodeType": "ExpressionStatement", + "src": "1241:48:2" + }, + { + "expression": { + "argumentTypes": null, + "id": 442, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 440, + "name": "data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 405, + "src": "1299:29:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 441, + "name": "_data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 423, + "src": "1331:30:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "1299:62:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 443, + "nodeType": "ExpressionStatement", + "src": "1299:62:2" + }, + { + "expression": { + "argumentTypes": null, + "id": 450, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 444, + "name": "amb", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 407, + "src": "1371:3:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "expression": { + "argumentTypes": null, + "id": 446, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 409, + "src": "1382:14:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 447, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "bridgeContract", + "nodeType": "MemberAccess", + "referencedDeclaration": 2946, + "src": "1382:29:2", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$__$returns$_t_address_$", + "typeString": "function () view external returns (address)" + } + }, + "id": 448, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1382:31:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 445, + "name": "IAMB", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2912, + "src": "1377:4:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_IAMB_$2912_$", + "typeString": "type(contract IAMB)" + } + }, + "id": 449, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1377:37:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "src": "1371:43:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "id": 451, + "nodeType": "ExpressionStatement", + "src": "1371:43:2" + } + ] + }, + "documentation": null, + "id": 453, + "implemented": true, + "kind": "constructor", + "modifiers": [ + { + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 426, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "1195:3:2", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 427, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "1195:10:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "id": 428, + "modifierName": { + "argumentTypes": null, + "id": 425, + "name": "Ownable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3180, + "src": "1187:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_Ownable_$3180_$", + "typeString": "type(contract Ownable)" + } + }, + "nodeType": "ModifierInvocation", + "src": "1187:19:2" + } + ], + "name": "", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 424, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 419, + "mutability": "mutable", + "name": "_token", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 453, + "src": "1099:14:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 418, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1099:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 421, + "mutability": "mutable", + "name": "_token_mediator", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 453, + "src": "1115:23:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 420, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1115:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 423, + "mutability": "mutable", + "name": "_data_union_sidechain_template", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 453, + "src": "1140:38:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 422, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1140:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1098:81:2" + }, + "returnParameters": { + "id": 429, + "nodeType": "ParameterList", + "parameters": [], + "src": "1207:0:2" + }, + "scope": 666, + "src": "1087:334:2", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 456, + "nodeType": "Block", + "src": "1480:2:2", + "statements": [] + }, + "documentation": null, + "id": 457, + "implemented": true, + "kind": "receive", + "modifiers": [], + "name": "", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 454, + "nodeType": "ParameterList", + "parameters": [], + "src": "1460:2:2" + }, + "returnParameters": { + "id": 455, + "nodeType": "ParameterList", + "parameters": [], + "src": "1480:0:2" + }, + "scope": 666, + "src": "1453:29:2", + "stateMutability": "payable", + "virtual": false, + "visibility": "external" + }, + { + "body": { + "id": 477, + "nodeType": "Block", + "src": "1543:122:2", + "statements": [ + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 466, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 464, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 459, + "src": "1556:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 465, + "name": "newDUInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 411, + "src": "1563:15:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1556:22:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 468, + "nodeType": "IfStatement", + "src": "1553:34:2", + "trueBody": { + "expression": null, + "functionReturnParameters": 463, + "id": 467, + "nodeType": "Return", + "src": "1580:7:2" + } + }, + { + "expression": { + "argumentTypes": null, + "id": 471, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 469, + "name": "newDUInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 411, + "src": "1596:15:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 470, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 459, + "src": "1614:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1596:21:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 472, + "nodeType": "ExpressionStatement", + "src": "1596:21:2" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 474, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 459, + "src": "1654:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 473, + "name": "UpdateNewDUInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 387, + "src": "1632:21:2", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 475, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1632:26:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 476, + "nodeType": "EmitStatement", + "src": "1627:31:2" + } + ] + }, + "documentation": null, + "functionSelector": "4e51a863", + "id": 478, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 462, + "modifierName": { + "argumentTypes": null, + "id": 461, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "1533:9:2", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "1533:9:2" + } + ], + "name": "setNewDUInitialEth", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 460, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 459, + "mutability": "mutable", + "name": "val", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 478, + "src": "1516:8:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 458, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1516:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1515:10:2" + }, + "returnParameters": { + "id": 463, + "nodeType": "ParameterList", + "parameters": [], + "src": "1543:0:2" + }, + "scope": 666, + "src": "1488:177:2", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 498, + "nodeType": "Block", + "src": "1731:137:2", + "statements": [ + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 487, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 485, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 480, + "src": "1744:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 486, + "name": "newDUOwnerInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 413, + "src": "1751:20:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1744:27:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 489, + "nodeType": "IfStatement", + "src": "1741:39:2", + "trueBody": { + "expression": null, + "functionReturnParameters": 484, + "id": 488, + "nodeType": "Return", + "src": "1773:7:2" + } + }, + { + "expression": { + "argumentTypes": null, + "id": 492, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 490, + "name": "newDUOwnerInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 413, + "src": "1789:20:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 491, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 480, + "src": "1812:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1789:26:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 493, + "nodeType": "ExpressionStatement", + "src": "1789:26:2" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 495, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 480, + "src": "1857:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 494, + "name": "UpdateNewDUOwnerInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 391, + "src": "1830:26:2", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 496, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1830:31:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 497, + "nodeType": "EmitStatement", + "src": "1825:36:2" + } + ] + }, + "documentation": null, + "functionSelector": "f0ef0b06", + "id": 499, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 483, + "modifierName": { + "argumentTypes": null, + "id": 482, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "1721:9:2", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "1721:9:2" + } + ], + "name": "setNewDUOwnerInitialEth", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 481, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 480, + "mutability": "mutable", + "name": "val", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 499, + "src": "1704:8:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 479, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1704:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1703:10:2" + }, + "returnParameters": { + "id": 484, + "nodeType": "ParameterList", + "parameters": [], + "src": "1731:0:2" + }, + "scope": 666, + "src": "1671:197:2", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 519, + "nodeType": "Block", + "src": "1933:141:2", + "statements": [ + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 508, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 506, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 501, + "src": "1946:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 507, + "name": "defaultNewMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 415, + "src": "1953:19:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1946:26:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 510, + "nodeType": "IfStatement", + "src": "1943:38:2", + "trueBody": { + "expression": null, + "functionReturnParameters": 505, + "id": 509, + "nodeType": "Return", + "src": "1974:7:2" + } + }, + { + "expression": { + "argumentTypes": null, + "id": 513, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 511, + "name": "defaultNewMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 415, + "src": "1990:19:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 512, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 501, + "src": "2012:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "1990:25:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 514, + "nodeType": "ExpressionStatement", + "src": "1990:25:2" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 516, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 501, + "src": "2063:3:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 515, + "name": "UpdateDefaultNewMemberInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 395, + "src": "2030:32:2", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 517, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2030:37:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 518, + "nodeType": "EmitStatement", + "src": "2025:42:2" + } + ] + }, + "documentation": null, + "functionSelector": "37dd8b05", + "id": 520, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 504, + "modifierName": { + "argumentTypes": null, + "id": 503, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "1923:9:2", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "1923:9:2" + } + ], + "name": "setNewMemberInitialEth", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 502, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 501, + "mutability": "mutable", + "name": "val", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 520, + "src": "1906:8:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 500, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1906:4:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1905:10:2" + }, + "returnParameters": { + "id": 505, + "nodeType": "ParameterList", + "parameters": [], + "src": "1933:0:2" + }, + "scope": 666, + "src": "1874:200:2", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 543, + "nodeType": "Block", + "src": "2187:139:2", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 529, + "name": "data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 405, + "src": "2240:29:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 532, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "2279:4:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionFactorySidechain_$666", + "typeString": "contract DataUnionFactorySidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionFactorySidechain_$666", + "typeString": "contract DataUnionFactorySidechain" + } + ], + "id": 531, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2271:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 530, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2271:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 533, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2271:13:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 538, + "name": "mainet_address", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 522, + "src": "2302:14:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 537, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2294:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_uint256_$", + "typeString": "type(uint256)" + }, + "typeName": { + "id": 536, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "2294:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 539, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2294:23:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 535, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2286:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes32_$", + "typeString": "type(bytes32)" + }, + "typeName": { + "id": 534, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "2286:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 540, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2286:32:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "expression": { + "argumentTypes": null, + "id": 527, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "2204:8:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 528, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "predictCloneAddressCreate2", + "nodeType": "MemberAccess", + "referencedDeclaration": 61, + "src": "2204:35:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_address_$_t_address_$_t_bytes32_$returns$_t_address_$", + "typeString": "function (address,address,bytes32) pure returns (address)" + } + }, + "id": 541, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2204:115:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "functionReturnParameters": 526, + "id": 542, + "nodeType": "Return", + "src": "2197:122:2" + } + ] + }, + "documentation": null, + "functionSelector": "17c2a98c", + "id": 544, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "sidechainAddress", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 523, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 522, + "mutability": "mutable", + "name": "mainet_address", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 544, + "src": "2107:22:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 521, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2107:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2106:24:2" + }, + "returnParameters": { + "id": 526, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 525, + "mutability": "mutable", + "name": "proxy", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 544, + "src": "2168:13:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 524, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2168:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2167:15:2" + }, + "scope": 666, + "src": "2081:245:2", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 664, + "nodeType": "Block", + "src": "2534:1168:2", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 561, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 555, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "2552:3:2", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 556, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "2552:10:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 559, + "name": "amb", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 407, + "src": "2574:3:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + ], + "id": 558, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2566:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 557, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2566:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 560, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2566:12:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "2552:26:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6f6e6c795f414d42", + "id": 562, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2580:10:2", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_7698dac7a74e78fd5d71d9ff5c3f048a6bfa330cbd49f847a9726ba9ad094965", + "typeString": "literal_string \"only_AMB\"" + }, + "value": "only_AMB" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_7698dac7a74e78fd5d71d9ff5c3f048a6bfa330cbd49f847a9726ba9ad094965", + "typeString": "literal_string \"only_AMB\"" + } + ], + "id": 554, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "2544:7:2", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 563, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2544:47:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 564, + "nodeType": "ExpressionStatement", + "src": "2544:47:2" + }, + { + "assignments": [ + 566 + ], + "declarations": [ + { + "constant": false, + "id": 566, + "mutability": "mutable", + "name": "duMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 664, + "src": "2601:17:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 565, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2601:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 570, + "initialValue": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "expression": { + "argumentTypes": null, + "id": 567, + "name": "amb", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 407, + "src": "2621:3:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "id": 568, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "messageSender", + "nodeType": "MemberAccess", + "referencedDeclaration": 2852, + "src": "2621:17:2", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$__$returns$_t_address_$", + "typeString": "function () view external returns (address)" + } + }, + "id": 569, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2621:19:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "2601:39:2" + }, + { + "assignments": [ + 572 + ], + "declarations": [ + { + "constant": false, + "id": 572, + "mutability": "mutable", + "name": "salt", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 664, + "src": "2650:12:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 571, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "2650:7:2", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 580, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 577, + "name": "duMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 566, + "src": "2681:9:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 576, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2673:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_uint256_$", + "typeString": "type(uint256)" + }, + "typeName": { + "id": 575, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "2673:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 578, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2673:18:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 574, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2665:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes32_$", + "typeString": "type(bytes32)" + }, + "typeName": { + "id": 573, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "2665:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 579, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2665:27:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "2650:42:2" + }, + { + "assignments": [ + 582 + ], + "declarations": [ + { + "constant": false, + "id": 582, + "mutability": "mutable", + "name": "data", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 664, + "src": "2702:17:2", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 581, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "2702:5:2", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 596, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "696e697469616c697a6528616464726573732c616464726573732c616464726573735b5d2c616464726573732c616464726573732c75696e7432353629", + "id": 585, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2746:63:2", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_015c7f51e2589abfef5a1063cf7321fa0da4fb1756a7b671e8b1ca6858d2b75f", + "typeString": "literal_string \"initialize(address,address,address[],address,address,uint256)\"" + }, + "value": "initialize(address,address,address[],address,address,uint256)" + }, + { + "argumentTypes": null, + "id": 586, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 546, + "src": "2823:5:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 587, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 417, + "src": "2842:5:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 588, + "name": "agents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 549, + "src": "2861:6:2", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 591, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 409, + "src": "2889:14:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + ], + "id": 590, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2881:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 589, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2881:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 592, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2881:23:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 593, + "name": "duMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 566, + "src": "2918:9:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 594, + "name": "defaultNewMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 415, + "src": "2941:19:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_015c7f51e2589abfef5a1063cf7321fa0da4fb1756a7b671e8b1ca6858d2b75f", + "typeString": "literal_string \"initialize(address,address,address[],address,address,uint256)\"" + }, + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 583, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "2722:3:2", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 584, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encodeWithSignature", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "2722:23:2", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencodewithsignature_pure$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (string memory) pure returns (bytes memory)" + } + }, + "id": 595, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2722:248:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "2702:268:2" + }, + { + "assignments": [ + 598 + ], + "declarations": [ + { + "constant": false, + "id": 598, + "mutability": "mutable", + "name": "du", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 664, + "src": "2980:18:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + "typeName": { + "id": 597, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2980:15:2", + "stateMutability": "payable", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 608, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 603, + "name": "data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 405, + "src": "3063:29:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "expression": { + "argumentTypes": null, + "id": 601, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "3040:8:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 602, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "cloneBytecode", + "nodeType": "MemberAccess", + "referencedDeclaration": 18, + "src": "3040:22:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_address_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (address) pure returns (bytes memory)" + } + }, + "id": 604, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3040:53:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "id": 605, + "name": "data", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 582, + "src": "3095:4:2", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "id": 606, + "name": "salt", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 572, + "src": "3101:4:2", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "expression": { + "argumentTypes": null, + "id": 599, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "3001:8:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 600, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "deployCodeAndInitUsingCreate2", + "nodeType": "MemberAccess", + "referencedDeclaration": 108, + "src": "3001:38:2", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_bytes_memory_ptr_$_t_bytes_memory_ptr_$_t_bytes32_$returns$_t_address_payable_$", + "typeString": "function (bytes memory,bytes memory,bytes32) returns (address payable)" + } + }, + "id": 607, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3001:105:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "2980:126:2" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 610, + "name": "duMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 566, + "src": "3140:9:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 611, + "name": "du", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 598, + "src": "3151:2:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 612, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 546, + "src": "3155:5:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 613, + "name": "data_union_sidechain_template", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 405, + "src": "3162:29:2", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 609, + "name": "SidechainDUCreated", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 383, + "src": "3121:18:2", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_address_$_t_address_$_t_address_$returns$__$", + "typeString": "function (address,address,address,address)" + } + }, + "id": 614, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3121:71:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 615, + "nodeType": "EmitStatement", + "src": "3116:76:2" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 626, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 618, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 616, + "name": "newDUInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 411, + "src": "3256:15:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 617, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3275:1:2", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "3256:20:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "&&", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 625, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 621, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "3288:4:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionFactorySidechain_$666", + "typeString": "contract DataUnionFactorySidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionFactorySidechain_$666", + "typeString": "contract DataUnionFactorySidechain" + } + ], + "id": 620, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "3280:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 619, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3280:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 622, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3280:13:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "id": 623, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "balance", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "3280:21:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">=", + "rightExpression": { + "argumentTypes": null, + "id": 624, + "name": "newDUInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 411, + "src": "3305:15:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "3280:40:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "3256:64:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 638, + "nodeType": "IfStatement", + "src": "3252:195:2", + "trueBody": { + "id": 637, + "nodeType": "Block", + "src": "3322:125:2", + "statements": [ + { + "condition": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 629, + "name": "newDUInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 411, + "src": "3348:15:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 627, + "name": "du", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 598, + "src": "3340:2:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "id": 628, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "send", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "3340:7:2", + "typeDescriptions": { + "typeIdentifier": "t_function_send_nonpayable$_t_uint256_$returns$_t_bool_$", + "typeString": "function (uint256) returns (bool)" + } + }, + "id": 630, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3340:24:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 636, + "nodeType": "IfStatement", + "src": "3336:101:2", + "trueBody": { + "id": 635, + "nodeType": "Block", + "src": "3366:71:2", + "statements": [ + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 632, + "name": "newDUInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 411, + "src": "3406:15:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 631, + "name": "DUInitialEthSent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 399, + "src": "3389:16:2", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 633, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3389:33:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 634, + "nodeType": "EmitStatement", + "src": "3384:38:2" + } + ] + } + } + ] + } + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 649, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 641, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 639, + "name": "newDUOwnerInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 413, + "src": "3460:20:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 640, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3484:1:2", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "3460:25:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "&&", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 648, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 644, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "3497:4:2", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionFactorySidechain_$666", + "typeString": "contract DataUnionFactorySidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionFactorySidechain_$666", + "typeString": "contract DataUnionFactorySidechain" + } + ], + "id": 643, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "3489:7:2", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 642, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3489:7:2", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 645, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3489:13:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "id": 646, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "balance", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "3489:21:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">=", + "rightExpression": { + "argumentTypes": null, + "id": 647, + "name": "newDUOwnerInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 413, + "src": "3514:20:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "3489:45:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "3460:74:2", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 661, + "nodeType": "IfStatement", + "src": "3456:221:2", + "trueBody": { + "id": 660, + "nodeType": "Block", + "src": "3536:141:2", + "statements": [ + { + "condition": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 652, + "name": "newDUOwnerInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 413, + "src": "3565:20:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 650, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 546, + "src": "3554:5:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "id": 651, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "send", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "3554:10:2", + "typeDescriptions": { + "typeIdentifier": "t_function_send_nonpayable$_t_uint256_$returns$_t_bool_$", + "typeString": "function (uint256) returns (bool)" + } + }, + "id": 653, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3554:32:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 659, + "nodeType": "IfStatement", + "src": "3550:117:2", + "trueBody": { + "id": 658, + "nodeType": "Block", + "src": "3588:79:2", + "statements": [ + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 655, + "name": "newDUOwnerInitialEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 413, + "src": "3631:20:2", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 654, + "name": "OwnerInitialEthSent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 403, + "src": "3611:19:2", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 656, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3611:41:2", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 657, + "nodeType": "EmitStatement", + "src": "3606:46:2" + } + ] + } + } + ] + } + }, + { + "expression": { + "argumentTypes": null, + "id": 662, + "name": "du", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 598, + "src": "3693:2:2", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "functionReturnParameters": 553, + "id": 663, + "nodeType": "Return", + "src": "3686:9:2" + } + ] + }, + "documentation": null, + "functionSelector": "325ff66f", + "id": 665, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "deployNewDUSidechain", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 550, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 546, + "mutability": "mutable", + "name": "owner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 665, + "src": "2461:21:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + "typeName": { + "id": 545, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2461:15:2", + "stateMutability": "payable", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 549, + "mutability": "mutable", + "name": "agents", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 665, + "src": "2484:23:2", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 547, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2484:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 548, + "length": null, + "nodeType": "ArrayTypeName", + "src": "2484:9:2", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2460:48:2" + }, + "returnParameters": { + "id": 553, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 552, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 665, + "src": "2525:7:2", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 551, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2525:7:2", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2524:9:2" + }, + "scope": 666, + "src": "2431:1271:2", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + } + ], + "scope": 667, + "src": "324:3380:2" + } + ], + "src": "0:3705:2" + }, + "compiler": { + "name": "solc", + "version": "0.6.6+commit.6c089d02.Emscripten.clang" + }, + "networks": {}, + "schemaVersion": "3.2.3", + "updatedAt": "2020-12-09T11:44:39.469Z", + "devdoc": { + "methods": { + "claimOwnership()": { + "details": "Allows the pendingOwner address to finalize the transfer." + }, + "transferOwnership(address)": { + "details": "Allows the current owner to set the pendingOwner address.", + "params": { + "newOwner": "The address to transfer ownership to." + } + } + } + }, + "userdoc": { + "methods": {} + } +} \ No newline at end of file diff --git a/contracts/DataUnionMainnet.json b/contracts/DataUnionMainnet.json new file mode 100644 index 000000000..62394dc33 --- /dev/null +++ b/contracts/DataUnionMainnet.json @@ -0,0 +1,14011 @@ +{ + "contractName": "DataUnionMainnet", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "adminFee", + "type": "uint256" + } + ], + "name": "AdminFeeChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "AdminFeeCharged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "admin", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "AdminFeesWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "RevenueReceived", + "type": "event" + }, + { + "inputs": [], + "name": "adminFeeFraction", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "amb", + "outputs": [ + { + "internalType": "contract IAMB", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "autoSendAdminFee", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sidechain_DU_factory", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sidechain_maxgas", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sidechain_template_DU", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "contract ERC20", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token_mediator", + "outputs": [ + { + "internalType": "contract ITokenMediator", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalAdminFees", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalAdminFeesWithdrawn", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalEarnings", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "version", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "_token", + "type": "address" + }, + { + "internalType": "address", + "name": "_token_mediator", + "type": "address" + }, + { + "internalType": "address", + "name": "_sidechain_DU_factory", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_sidechain_maxgas", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_sidechain_template_DU", + "type": "address" + }, + { + "internalType": "address", + "name": "_owner", + "type": "address" + }, + { + "internalType": "uint256", + "name": "_adminFeeFraction", + "type": "uint256" + }, + { + "internalType": "address[]", + "name": "agents", + "type": "address[]" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isInitialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "newAdminFee", + "type": "uint256" + } + ], + "name": "setAdminFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "autoSend", + "type": "bool" + } + ], + "name": "setAutoSendAdminFee", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "agents", + "type": "address[]" + } + ], + "name": "deployNewDUSidechain", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "sidechainAddress", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "name": "onPurchase", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "adminFeesWithdrawable", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "unaccountedTokens", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "sendTokensToBridge", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "withdrawAdminFees", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + } + ], + "metadata": "{\"compiler\":{\"version\":\"0.6.6+commit.6c089d02\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"adminFee\",\"type\":\"uint256\"}],\"name\":\"AdminFeeChanged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"AdminFeeCharged\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"admin\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"AdminFeesWithdrawn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"RevenueReceived\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"adminFeeFraction\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"adminFeesWithdrawable\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"amb\",\"outputs\":[{\"internalType\":\"contract IAMB\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"autoSendAdminFee\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"agents\",\"type\":\"address[]\"}],\"name\":\"deployNewDUSidechain\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_token_mediator\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_sidechain_DU_factory\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_sidechain_maxgas\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_sidechain_template_DU\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_owner\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_adminFeeFraction\",\"type\":\"uint256\"},{\"internalType\":\"address[]\",\"name\":\"agents\",\"type\":\"address[]\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isInitialized\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"name\":\"onPurchase\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sendTokensToBridge\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"newAdminFee\",\"type\":\"uint256\"}],\"name\":\"setAdminFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bool\",\"name\":\"autoSend\",\"type\":\"bool\"}],\"name\":\"setAutoSendAdminFee\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sidechainAddress\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sidechain_DU_factory\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sidechain_maxgas\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"sidechain_template_DU\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token\",\"outputs\":[{\"internalType\":\"contract ERC20\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token_mediator\",\"outputs\":[{\"internalType\":\"contract ITokenMediator\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalAdminFees\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalAdminFeesWithdrawn\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalEarnings\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unaccountedTokens\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"version\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"withdrawAdminFees\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}],\"devdoc\":{\"methods\":{\"claimOwnership()\":{\"details\":\"Allows the pendingOwner address to finalize the transfer.\"},\"setAdminFee(uint256)\":{\"params\":{\"newAdminFee\":\"fixed-point decimal in the same way as ether: 50% === 0.5 ether === \\\"500000000000000000\\\"\"}},\"transferOwnership(address)\":{\"details\":\"Allows the current owner to set the pendingOwner address.\",\"params\":{\"newOwner\":\"The address to transfer ownership to.\"}}}},\"userdoc\":{\"methods\":{\"setAdminFee(uint256)\":{\"notice\":\"Admin fee as a fraction of revenue.\"}}}},\"settings\":{\"compilationTarget\":{\"/home/heynow/streamr/data-union-solidity/contracts/DataUnionMainnet.sol\":\"DataUnionMainnet\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/heynow/streamr/data-union-solidity/contracts/CloneLib.sol\":{\"keccak256\":\"0xf9082fe41a9e05b37cf87f0d1d67ca16d32c4100a2a27a6fc9d8f3f8364a7486\",\"urls\":[\"bzz-raw://b928f45b1904c399db9508c093604531bd215afeb4686827ab46daa7aee1938e\",\"dweb:/ipfs/QmdnBUvpCDc93fHZe7o6bitGRZo4PFTx9NprgziqHsVHFw\"]},\"/home/heynow/streamr/data-union-solidity/contracts/DataUnionMainnet.sol\":{\"keccak256\":\"0x4365bae70a1d10cf57cbc9d323f33824cacb8bba41308bb65954b30b65f22868\",\"urls\":[\"bzz-raw://065a556347a0862e0c23b9c70c527736bca2c6c146dbac976587dd963b62eea4\",\"dweb:/ipfs/Qmf6ujfdwgHhp9oMyb9S6RjY8N2ckUpk4HhUdJM4FXkQVQ\"]},\"/home/heynow/streamr/data-union-solidity/contracts/IAMB.sol\":{\"keccak256\":\"0x63b46d3087a8ba558cb36d99ffe5b2f0241446adc4c8df4219ce86f97727e168\",\"urls\":[\"bzz-raw://66d9c8c817e90b25f28b3d79a69b440018b49633a7bbaf6fe4d1788b2ff5e6eb\",\"dweb:/ipfs/QmSdrqpaZ3R3NGFuF6jnbo8cCgV5ghdW5iWrnV59qavYP2\"]},\"/home/heynow/streamr/data-union-solidity/contracts/ITokenMediator.sol\":{\"keccak256\":\"0x1f21e943f4e125dd1c4353db29880b63f8fa7a299ba862432c8a4e14807873e2\",\"urls\":[\"bzz-raw://4ddc233243efce6c2582b63e3be28c4f90f32d60b5d4b6fe8331106d970a8435\",\"dweb:/ipfs/QmQzLwvxU3ixfLbdpuYdpqvW61xjPbnXDTaaWNjRJUhvSK\"]},\"/home/heynow/streamr/data-union-solidity/contracts/Ownable.sol\":{\"keccak256\":\"0x0d96dac82f3bf17d2cdc90863a61c8957622c305bc3bbf9cd8f47facfa71dea1\",\"urls\":[\"bzz-raw://1cd621b81754be8073fc89bd2bc864e7b0169ba5f153edc5b9212a19f4890aa3\",\"dweb:/ipfs/QmcE5HTf3pptr2s7oiDqYtotCLrb736g8N7YyRZj8k5mHg\"]},\"/home/heynow/streamr/data-union-solidity/contracts/PurchaseListener.sol\":{\"keccak256\":\"0x2eed0264d535450c36dc5385247e200d5e1defaecbf20ace3d2eee6d5bcd78cd\",\"urls\":[\"bzz-raw://292464cafc307356e6c53d613c8bbb3cc2ccb60212d5b0e6e8a91f5304fbd67b\",\"dweb:/ipfs/QmX3MMED2EYBTHVwnaCVQEf3yd2e3M2MfR4paDcuBEtmLD\"]},\"openzeppelin-solidity/contracts/GSN/Context.sol\":{\"keccak256\":\"0xdb26cbf4d028490f49831a7865c2fe1b28db44b535ca8d343785a3b768aae183\",\"urls\":[\"bzz-raw://840b14ce0315c47d49ba328f1f9fa7654ded1c9e1559e6c5e777a7b2dc28bf0a\",\"dweb:/ipfs/QmTLLabn4wcfGro9LEmUXUN2nwKqZSotXMvjDCLXEnLtZP\"]},\"openzeppelin-solidity/contracts/math/SafeMath.sol\":{\"keccak256\":\"0x9a9cf02622cd7a64261b10534fc3260449da25c98c9e96d1b4ae8110a20e5806\",\"urls\":[\"bzz-raw://2df142592d1dc267d9549049ee3317fa190d2f87eaa565f86ab05ec83f7ab8f5\",\"dweb:/ipfs/QmSkJtcfWo7c42KnL5hho6GFxK6HRNV91XABx1P7xDtfLV\"]},\"openzeppelin-solidity/contracts/token/ERC20/ERC20.sol\":{\"keccak256\":\"0xf204d98eef08edacf5a02a2af1516ea4febdb6aba7a1ae5ac8deb6e568fd3dbb\",\"urls\":[\"bzz-raw://c4dea62bffbd180772a6cfe7cb90e3a045c52d6b502bdb2fdff83193da42d8d0\",\"dweb:/ipfs/QmW8qGZ5nngajmv5Aamdrpkeuq7S5YCVaR7rtcqNekKHtK\"]},\"openzeppelin-solidity/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0x5c26b39d26f7ed489e555d955dcd3e01872972e71fdd1528e93ec164e4f23385\",\"urls\":[\"bzz-raw://efdc632af6960cf865dbc113665ea1f5b90eab75cc40ec062b2f6ae6da582017\",\"dweb:/ipfs/QmfAZFDuG62vxmAN9DnXApv7e7PMzPqi4RkqqZHLMSQiY5\"]},\"openzeppelin-solidity/contracts/utils/Address.sol\":{\"keccak256\":\"0xdfb4f812600ba4ce6738c35584ceb8c9433472583051b48ba5b1f66cb758a498\",\"urls\":[\"bzz-raw://df02dffe1c1de089d9b4f6192f0dcf464526f2230f420b3deec4645e0cdd2bff\",\"dweb:/ipfs/QmcqXGAU3KJqwrgUVoGJ2W8osomhSJ4R5kdsRpbuW3fELS\"]}},\"version\":1}", + "bytecode": "0x608060405234801561001057600080fd5b50600080546001600160a01b0319169055611652806100306000396000f3fe608060405234801561001057600080fd5b50600436106101a95760003560e01c8063692199d4116100f9578063c5a8c91f11610097578063e30c397811610071578063e30c3978146103a7578063f2fde38b146103af578063fb6470c9146103d5578063fc0c546a146104b8576101a9565b8063c5a8c91f1461038f578063d35cec4014610397578063e22ec1121461039f576101a9565b806399dd1c81116100d357806399dd1c81146102d45780639eeba07c14610377578063a65c87d91461037f578063a913f41d14610387576101a9565b8063692199d4146102a75780638beb60b6146102af5780638da5cb5b146102cc576101a9565b80632efc1007116101665780634a439cc0116101405780634a439cc0146102515780634e71e0c81461028f57806354fd4d501461029757806361feacff1461029f576101a9565b80632efc10071461022557806337b43a941461022d578063392e53cd14610235576101a9565b80630419b45a146101ae5780630f3afcbe146101c85780631062b39a146101e9578063132b41941461020d57806313fd3c56146102155780632df3eba41461021d575b600080fd5b6101b66104c0565b60408051918252519081900360200190f35b6101e7600480360360208110156101de57600080fd5b50351515610606565b005b6101f1610664565b604080516001600160a01b039092168252519081900360200190f35b6101b6610673565b6101b6610708565b6101b6610721565b6101b6610727565b6101f1610d35565b61023d610d55565b604080519115158252519081900360200190f35b61023d600480360360a081101561026757600080fd5b508035906001600160a01b036020820135169060408101359060608101359060800135610d66565b6101e7610d7d565b6101b6610e33565b6101b6610e38565b6101b6610e3e565b6101e7600480360360208110156102c557600080fd5b5035610e44565b6101f1610f18565b6101e7600480360360208110156102ea57600080fd5b81019060208101813564010000000081111561030557600080fd5b82018360208201111561031757600080fd5b8035906020019184602083028401116401000000008311171561033957600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610f27945050505050565b61023d6110cb565b6101f16110d4565b6101b66110e3565b6101f16110e9565b6101b66110f8565b6101f16110fe565b6101f161110d565b6101e7600480360360208110156103c557600080fd5b50356001600160a01b031661111c565b6101e760048036036101008110156103ec57600080fd5b6001600160a01b03823581169260208101358216926040820135831692606083013592608081013582169260a08201359092169160c08201359190810190610100810160e082013564010000000081111561044657600080fd5b82018360208201111561045857600080fd5b8035906020019184602083028401116401000000008311171561047a57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550611189945050505050565b6101f16112f2565b6000806104cb610708565b9050806104dc576000915050610603565b600a546104ef908263ffffffff61130116565b600a55600654600080546040805163a9059cbb60e01b81526001600160a01b039283166004820152602481018690529051919093169263a9059cbb9260448083019360209390929083900390910190829087803b15801561054f57600080fd5b505af1158015610563573d6000803e3d6000fd5b505050506040513d602081101561057957600080fd5b50516105be576040805162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c97d9985a5b1959608a1b604482015290519081900360640190fd5b6000546040805183815290516001600160a01b03909216917fcdcaff67ac16639664e5f9343c9223a1dc9c972ec367b69ae9fc1325c7be54749181900360200190a290505b90565b6000546001600160a01b03163314610651576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b600b805460ff1916911515919091179055565b6002546001600160a01b031681565b6000610703610680610708565b600654604080516370a0823160e01b815230600482015290516001600160a01b03909216916370a0823191602480820192602092909190829003018186803b1580156106cb57600080fd5b505afa1580156106df573d6000803e3d6000fd5b505050506040513d60208110156106f557600080fd5b50519063ffffffff61136416565b905090565b6000610703600a5460095461136490919063ffffffff16565b600c5481565b600080610732610673565b905080610743576000915050610603565b6040805182815290517f41b06c6e0a1531dcb4b86d53ec6268666aa12d55775f8e5a63596fc935cdcc229181900360200190a160006107a5670de0b6b3a7640000610799600854856113a690919063ffffffff16565b9063ffffffff6113ff16565b905060006107b9838363ffffffff61136416565b6009549091506107cf908363ffffffff61130116565b6009556040805183815290517f538d1b2114be2374c7010694167f3db7f2d56f864a4e1555582b9716b7d11c3d9181900360200190a1600b5460ff161561081a576108186104c0565b505b6006546003546040805163095ea7b360e01b81526001600160a01b0392831660048201526000602482018190529151929093169263095ea7b39260448083019360209383900390910190829087803b15801561087557600080fd5b505af1158015610889573d6000803e3d6000fd5b505050506040513d602081101561089f57600080fd5b50516108e3576040805162461bcd60e51b815260206004820152600e60248201526d185c1c1c9bdd9957d9985a5b195960921b604482015290519081900360640190fd5b6006546003546040805163095ea7b360e01b81526001600160a01b039283166004820152602481018590529051919092169163095ea7b39160448083019260209291908290030181600087803b15801561093c57600080fd5b505af1158015610950573d6000803e3d6000fd5b505050506040513d602081101561096657600080fd5b50516109aa576040805162461bcd60e51b815260206004820152600e60248201526d185c1c1c9bdd9957d9985a5b195960921b604482015290519081900360640190fd5b6003546040805163437764df60e01b815290516000926001600160a01b03169163437764df916004808301926020929190829003018186803b1580156109ef57600080fd5b505afa158015610a03573d6000803e3d6000fd5b505050506040513d6020811015610a1957600080fd5b505190506358a8b61360e11b6001600160e01b031982161415610add576003546006546001600160a01b039182169163ad58bdd19116610a57610d35565b856040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b03168152602001836001600160a01b03166001600160a01b031681526020018281526020019350505050600060405180830381600087803b158015610ac057600080fd5b505af1158015610ad4573d6000803e3d6000fd5b50505050610ba2565b633b2cadab60e11b6001600160e01b031982161415610b5f576003546001600160a01b03166301e4f53a610b0f610d35565b846040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b158015610ac057600080fd5b6040805162461bcd60e51b8152602060048201526013602482015272756e6b6e6f776e5f6272696467655f6d6f646560681b604482015290519081900360640190fd5b610baa610673565b15610bee576040805162461bcd60e51b815260206004820152600f60248201526e1b9bdd17dd1c985b9cd9995c9c9959608a1b604482015290519081900360640190fd5b600c54610c01908363ffffffff61130116565b600c556040805160048152602481019091526020810180516001600160e01b031663331beb5f60e01b1790526002546001600160a01b031663dc8601b3610c46610d35565b6005546040516001600160e01b031960e085901b1681526001600160a01b038316600482019081526044820183905260606024830190815287516064840152875188949360840190602086019080838360005b83811015610cb1578181015183820152602001610c99565b50505050905090810190601f168015610cde5780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b158015610cff57600080fd5b505af1158015610d13573d6000803e3d6000fd5b505050506040513d6020811015610d2957600080fd5b50949550505050505090565b600754600454600091610703916001600160a01b03918216911630611441565b6006546001600160a01b0316151590565b6000610d70610727565b5060019695505050505050565b6001546001600160a01b03163314610dcf576040805162461bcd60e51b815260206004820152601060248201526f37b7363ca832b73234b733a7bbb732b960811b604482015290519081900360640190fd5b600154600080546040516001600160a01b0393841693909116917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a360018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b600290565b60095481565b60055481565b6000546001600160a01b03163314610e8f576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b670de0b6b3a7640000811115610edd576040805162461bcd60e51b815260206004820152600e60248201526d6572726f725f61646d696e46656560901b604482015290519081900360640190fd5b60088190556040805182815290517f11a80b766155f9b8f16a7da44d66269fd694cb1c247f4814244586f68dd534879181900360200190a150565b6000546001600160a01b031681565b60606000809054906101000a90046001600160a01b03168260405160240180836001600160a01b03166001600160a01b0316815260200180602001828103825283818151815260200191508051906020019060200280838360005b83811015610f9a578181015183820152602001610f82565b50506040805193909501838103601f190184528552505060208101805163325ff66f60e01b6001600160e01b0390911617815260025460048054600554965163dc8601b360e01b81526001600160a01b0391821692810183815260448201899052606060248301908152875160648401528751979e50929094169b5063dc8601b39a509198508b975091945090926084909101919080838360005b8381101561104d578181015183820152602001611035565b50505050905090810190601f16801561107a5780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b15801561109b57600080fd5b505af11580156110af573d6000803e3d6000fd5b505050506040513d60208110156110c557600080fd5b50505050565b600b5460ff1681565b6007546001600160a01b031681565b600a5481565b6003546001600160a01b031681565b60085481565b6004546001600160a01b031681565b6001546001600160a01b031681565b6000546001600160a01b03163314611167576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b611191610d55565b156111cf576040805162461bcd60e51b8152602060048201526009602482015268696e69745f6f6e636560b81b604482015290519081900360640190fd5b600b805460ff19166001179055600080546001600160a01b03199081163317909155600380546001600160a01b038a81169190931617908190556040805163cd59658360e01b81529051919092169163cd596583916004808301926020929190829003018186803b15801561124357600080fd5b505afa158015611257573d6000803e3d6000fd5b505050506040513d602081101561126d57600080fd5b5051600280546001600160a01b03199081166001600160a01b03938416179091556006805482168b84161790556004805482168984161790556005879055600780549091169186169190911790556112c482610e44565b600080546001600160a01b0319166001600160a01b0385161790556112e881610f27565b5050505050505050565b6006546001600160a01b031681565b60008282018381101561135b576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b90505b92915050565b600061135b83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f7700008152506114ad565b6000826113b55750600061135e565b828202828482816113c257fe5b041461135b5760405162461bcd60e51b81526004018080602001828103825260218152602001806115fc6021913960400191505060405180910390fd5b600061135b83836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f000000000000815250611544565b60008061144d856115a9565b8051602091820120604080516001600160f81b0319818501526bffffffffffffffffffffffff19606089901b1660218201526035810187905260558082019390935281518082039093018352607501905280519101209150509392505050565b6000818484111561153c5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156115015781810151838201526020016114e9565b50505050905090810190601f16801561152e5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b600081836115935760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156115015781810151838201526020016114e9565b50600083858161159f57fe5b0495945050505050565b604080516057810190915260378152733d602d80600a3d3981f3363d3d373d3d3d363d7360601b602082015260609190911b60348201526e5af43d82803e903d91602b57fd5bf360881b60488201529056fe536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77a2646970667358221220e5d0a1ab0c140b32158b43f1340885836a7589a9e16b58cccb1a4bfd9ef73ab964736f6c63430006060033", + "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101a95760003560e01c8063692199d4116100f9578063c5a8c91f11610097578063e30c397811610071578063e30c3978146103a7578063f2fde38b146103af578063fb6470c9146103d5578063fc0c546a146104b8576101a9565b8063c5a8c91f1461038f578063d35cec4014610397578063e22ec1121461039f576101a9565b806399dd1c81116100d357806399dd1c81146102d45780639eeba07c14610377578063a65c87d91461037f578063a913f41d14610387576101a9565b8063692199d4146102a75780638beb60b6146102af5780638da5cb5b146102cc576101a9565b80632efc1007116101665780634a439cc0116101405780634a439cc0146102515780634e71e0c81461028f57806354fd4d501461029757806361feacff1461029f576101a9565b80632efc10071461022557806337b43a941461022d578063392e53cd14610235576101a9565b80630419b45a146101ae5780630f3afcbe146101c85780631062b39a146101e9578063132b41941461020d57806313fd3c56146102155780632df3eba41461021d575b600080fd5b6101b66104c0565b60408051918252519081900360200190f35b6101e7600480360360208110156101de57600080fd5b50351515610606565b005b6101f1610664565b604080516001600160a01b039092168252519081900360200190f35b6101b6610673565b6101b6610708565b6101b6610721565b6101b6610727565b6101f1610d35565b61023d610d55565b604080519115158252519081900360200190f35b61023d600480360360a081101561026757600080fd5b508035906001600160a01b036020820135169060408101359060608101359060800135610d66565b6101e7610d7d565b6101b6610e33565b6101b6610e38565b6101b6610e3e565b6101e7600480360360208110156102c557600080fd5b5035610e44565b6101f1610f18565b6101e7600480360360208110156102ea57600080fd5b81019060208101813564010000000081111561030557600080fd5b82018360208201111561031757600080fd5b8035906020019184602083028401116401000000008311171561033957600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550610f27945050505050565b61023d6110cb565b6101f16110d4565b6101b66110e3565b6101f16110e9565b6101b66110f8565b6101f16110fe565b6101f161110d565b6101e7600480360360208110156103c557600080fd5b50356001600160a01b031661111c565b6101e760048036036101008110156103ec57600080fd5b6001600160a01b03823581169260208101358216926040820135831692606083013592608081013582169260a08201359092169160c08201359190810190610100810160e082013564010000000081111561044657600080fd5b82018360208201111561045857600080fd5b8035906020019184602083028401116401000000008311171561047a57600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550611189945050505050565b6101f16112f2565b6000806104cb610708565b9050806104dc576000915050610603565b600a546104ef908263ffffffff61130116565b600a55600654600080546040805163a9059cbb60e01b81526001600160a01b039283166004820152602481018690529051919093169263a9059cbb9260448083019360209390929083900390910190829087803b15801561054f57600080fd5b505af1158015610563573d6000803e3d6000fd5b505050506040513d602081101561057957600080fd5b50516105be576040805162461bcd60e51b815260206004820152600f60248201526e1d1c985b9cd9995c97d9985a5b1959608a1b604482015290519081900360640190fd5b6000546040805183815290516001600160a01b03909216917fcdcaff67ac16639664e5f9343c9223a1dc9c972ec367b69ae9fc1325c7be54749181900360200190a290505b90565b6000546001600160a01b03163314610651576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b600b805460ff1916911515919091179055565b6002546001600160a01b031681565b6000610703610680610708565b600654604080516370a0823160e01b815230600482015290516001600160a01b03909216916370a0823191602480820192602092909190829003018186803b1580156106cb57600080fd5b505afa1580156106df573d6000803e3d6000fd5b505050506040513d60208110156106f557600080fd5b50519063ffffffff61136416565b905090565b6000610703600a5460095461136490919063ffffffff16565b600c5481565b600080610732610673565b905080610743576000915050610603565b6040805182815290517f41b06c6e0a1531dcb4b86d53ec6268666aa12d55775f8e5a63596fc935cdcc229181900360200190a160006107a5670de0b6b3a7640000610799600854856113a690919063ffffffff16565b9063ffffffff6113ff16565b905060006107b9838363ffffffff61136416565b6009549091506107cf908363ffffffff61130116565b6009556040805183815290517f538d1b2114be2374c7010694167f3db7f2d56f864a4e1555582b9716b7d11c3d9181900360200190a1600b5460ff161561081a576108186104c0565b505b6006546003546040805163095ea7b360e01b81526001600160a01b0392831660048201526000602482018190529151929093169263095ea7b39260448083019360209383900390910190829087803b15801561087557600080fd5b505af1158015610889573d6000803e3d6000fd5b505050506040513d602081101561089f57600080fd5b50516108e3576040805162461bcd60e51b815260206004820152600e60248201526d185c1c1c9bdd9957d9985a5b195960921b604482015290519081900360640190fd5b6006546003546040805163095ea7b360e01b81526001600160a01b039283166004820152602481018590529051919092169163095ea7b39160448083019260209291908290030181600087803b15801561093c57600080fd5b505af1158015610950573d6000803e3d6000fd5b505050506040513d602081101561096657600080fd5b50516109aa576040805162461bcd60e51b815260206004820152600e60248201526d185c1c1c9bdd9957d9985a5b195960921b604482015290519081900360640190fd5b6003546040805163437764df60e01b815290516000926001600160a01b03169163437764df916004808301926020929190829003018186803b1580156109ef57600080fd5b505afa158015610a03573d6000803e3d6000fd5b505050506040513d6020811015610a1957600080fd5b505190506358a8b61360e11b6001600160e01b031982161415610add576003546006546001600160a01b039182169163ad58bdd19116610a57610d35565b856040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b03168152602001836001600160a01b03166001600160a01b031681526020018281526020019350505050600060405180830381600087803b158015610ac057600080fd5b505af1158015610ad4573d6000803e3d6000fd5b50505050610ba2565b633b2cadab60e11b6001600160e01b031982161415610b5f576003546001600160a01b03166301e4f53a610b0f610d35565b846040518363ffffffff1660e01b815260040180836001600160a01b03166001600160a01b0316815260200182815260200192505050600060405180830381600087803b158015610ac057600080fd5b6040805162461bcd60e51b8152602060048201526013602482015272756e6b6e6f776e5f6272696467655f6d6f646560681b604482015290519081900360640190fd5b610baa610673565b15610bee576040805162461bcd60e51b815260206004820152600f60248201526e1b9bdd17dd1c985b9cd9995c9c9959608a1b604482015290519081900360640190fd5b600c54610c01908363ffffffff61130116565b600c556040805160048152602481019091526020810180516001600160e01b031663331beb5f60e01b1790526002546001600160a01b031663dc8601b3610c46610d35565b6005546040516001600160e01b031960e085901b1681526001600160a01b038316600482019081526044820183905260606024830190815287516064840152875188949360840190602086019080838360005b83811015610cb1578181015183820152602001610c99565b50505050905090810190601f168015610cde5780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b158015610cff57600080fd5b505af1158015610d13573d6000803e3d6000fd5b505050506040513d6020811015610d2957600080fd5b50949550505050505090565b600754600454600091610703916001600160a01b03918216911630611441565b6006546001600160a01b0316151590565b6000610d70610727565b5060019695505050505050565b6001546001600160a01b03163314610dcf576040805162461bcd60e51b815260206004820152601060248201526f37b7363ca832b73234b733a7bbb732b960811b604482015290519081900360640190fd5b600154600080546040516001600160a01b0393841693909116917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a360018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b600290565b60095481565b60055481565b6000546001600160a01b03163314610e8f576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b670de0b6b3a7640000811115610edd576040805162461bcd60e51b815260206004820152600e60248201526d6572726f725f61646d696e46656560901b604482015290519081900360640190fd5b60088190556040805182815290517f11a80b766155f9b8f16a7da44d66269fd694cb1c247f4814244586f68dd534879181900360200190a150565b6000546001600160a01b031681565b60606000809054906101000a90046001600160a01b03168260405160240180836001600160a01b03166001600160a01b0316815260200180602001828103825283818151815260200191508051906020019060200280838360005b83811015610f9a578181015183820152602001610f82565b50506040805193909501838103601f190184528552505060208101805163325ff66f60e01b6001600160e01b0390911617815260025460048054600554965163dc8601b360e01b81526001600160a01b0391821692810183815260448201899052606060248301908152875160648401528751979e50929094169b5063dc8601b39a509198508b975091945090926084909101919080838360005b8381101561104d578181015183820152602001611035565b50505050905090810190601f16801561107a5780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b15801561109b57600080fd5b505af11580156110af573d6000803e3d6000fd5b505050506040513d60208110156110c557600080fd5b50505050565b600b5460ff1681565b6007546001600160a01b031681565b600a5481565b6003546001600160a01b031681565b60085481565b6004546001600160a01b031681565b6001546001600160a01b031681565b6000546001600160a01b03163314611167576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b611191610d55565b156111cf576040805162461bcd60e51b8152602060048201526009602482015268696e69745f6f6e636560b81b604482015290519081900360640190fd5b600b805460ff19166001179055600080546001600160a01b03199081163317909155600380546001600160a01b038a81169190931617908190556040805163cd59658360e01b81529051919092169163cd596583916004808301926020929190829003018186803b15801561124357600080fd5b505afa158015611257573d6000803e3d6000fd5b505050506040513d602081101561126d57600080fd5b5051600280546001600160a01b03199081166001600160a01b03938416179091556006805482168b84161790556004805482168984161790556005879055600780549091169186169190911790556112c482610e44565b600080546001600160a01b0319166001600160a01b0385161790556112e881610f27565b5050505050505050565b6006546001600160a01b031681565b60008282018381101561135b576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b90505b92915050565b600061135b83836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f7700008152506114ad565b6000826113b55750600061135e565b828202828482816113c257fe5b041461135b5760405162461bcd60e51b81526004018080602001828103825260218152602001806115fc6021913960400191505060405180910390fd5b600061135b83836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f000000000000815250611544565b60008061144d856115a9565b8051602091820120604080516001600160f81b0319818501526bffffffffffffffffffffffff19606089901b1660218201526035810187905260558082019390935281518082039093018352607501905280519101209150509392505050565b6000818484111561153c5760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b838110156115015781810151838201526020016114e9565b50505050905090810190601f16801561152e5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b600081836115935760405162461bcd60e51b81526020600482018181528351602484015283519092839260449091019190850190808383600083156115015781810151838201526020016114e9565b50600083858161159f57fe5b0495945050505050565b604080516057810190915260378152733d602d80600a3d3981f3363d3d373d3d3d363d7360601b602082015260609190911b60348201526e5af43d82803e903d91602b57fd5bf360881b60488201529056fe536166654d6174683a206d756c7469706c69636174696f6e206f766572666c6f77a2646970667358221220e5d0a1ab0c140b32158b43f1340885836a7589a9e16b58cccb1a4bfd9ef73ab964736f6c63430006060033", + "immutableReferences": {}, + "sourceMap": "356:6155:3:-:0;;;1479:43;5:9:-1;2:2;;;27:1;24;17:12;2:2;-1:-1;1516:1:3;577:14:12;;-1:-1:-1;;;;;;577:14:12;;;356:6155:3;;;;;;", + "deployedSourceMap": "356:6155:3:-:0;;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;356:6155:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12:1:-1;9;2:12;6118:391:3;;;:::i;:::-;;;;;;;;;;;;;;;;3002:105;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;3002:105:3;;;;:::i;:::-;;652:15;;;:::i;:::-;;;;-1:-1:-1;;;;;652:15:3;;;;;;;;;;;;;;4358:142;;;:::i;4222:130::-;;;:::i;1443:28::-;;;:::i;4507:1605::-;;;:::i;3380:195::-;;;:::i;2499:104::-;;;:::i;:::-;;;;;;;;;;;;;;;;;;4060:156;;;;;;15:3:-1;10;7:12;4:2;;;32:1;29;22:12;4:2;-1:-1;4060:156:3;;;-1:-1:-1;;;;;4060:156:3;;;;;;;;;;;;;;;;;;;;:::i;1123:226:12:-;;;:::i;1171:62:3:-;;;:::i;1057:29::-;;;:::i;756:31::-;;;:::i;2783:213::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;2783:213:3;;:::i;236:20:12:-;;;:::i;3114:260:3:-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;3114:260:3;;;;;;;;27:11:-1;11:28;;8:2;;;52:1;49;42:12;8:2;3114:260:3;;41:9:-1;34:4;18:14;14:25;11:40;8:2;;;64:1;61;54:12;8:2;3114:260:3;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;39:11;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;3114:260:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;3114:260:3;;-1:-1:-1;3114:260:3;;-1:-1:-1;;;;;3114:260:3:i;1136:28::-;;;:::i;978:36::-;;;:::i;1092:38::-;;;:::i;673:36::-;;;:::i;1020:31::-;;;:::i;715:35::-;;;:::i;262:27:12:-;;;:::i;929:102::-;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;929:102:12;-1:-1:-1;;;;;929:102:12;;:::i;1528:965:3:-;;;;;;15:3:-1;10;7:12;4:2;;;32:1;29;22:12;4:2;-1:-1;;;;;1528:965:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:11:-1;11:28;;8:2;;;52:1;49;42:12;8:2;1528:965:3;;41:9:-1;34:4;18:14;14:25;11:40;8:2;;;64:1;61;54:12;8:2;1528:965:3;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;39:11;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;1528:965:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;1528:965:3;;-1:-1:-1;1528:965:3;;-1:-1:-1;;;;;1528:965:3:i;793:18::-;;;:::i;6118:391::-;6163:7;6182:20;6205:23;:21;:23::i;:::-;6182:46;-1:-1:-1;6242:17:3;6238:31;;6268:1;6261:8;;;;;6238:31;6305:23;;:41;;6333:12;6305:41;:27;:41;:::i;:::-;6279:23;:67;6364:5;;;6379;;6364:35;;;-1:-1:-1;;;6364:35:3;;-1:-1:-1;;;;;6379:5:3;;;6364:35;;;;;;;;;;;;:5;;;;;:14;;:35;;;;;;;;;;;;;;;;;;;:5;:35;;;2:2:-1;;;;27:1;24;17:12;2:2;6364:35:3;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;6364:35:3;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;6364:35:3;6356:63;;;;;-1:-1:-1;;;6356:63:3;;;;;;;;;;;;-1:-1:-1;;;6356:63:3;;;;;;;;;;;;;;;6453:5;;6434:39;;;;;;;;-1:-1:-1;;;;;6453:5:3;;;;6434:39;;;;;;;;;6490:12;-1:-1:-1;6118:391:3;;:::o;3002:105::-;739:5:12;;-1:-1:-1;;;;;739:5:12;725:10;:19;717:41;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;;;;3073:16:3::1;:27:::0;;-1:-1:-1;;3073:27:3::1;::::0;::::1;;::::0;;;::::1;::::0;;3002:105::o;652:15::-;;;-1:-1:-1;;;;;652:15:3;;:::o;4358:142::-;4408:7;4434:59;4469:23;:21;:23::i;:::-;4434:5;;:30;;;-1:-1:-1;;;4434:30:3;;4458:4;4434:30;;;;;;-1:-1:-1;;;;;4434:5:3;;;;:15;;:30;;;;;;;;;;;;;;;:5;:30;;;2:2:-1;;;;27:1;24;17:12;2:2;4434:30:3;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;4434:30:3;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;4434:30:3;;:59;:34;:59;:::i;:::-;4427:66;;4358:142;:::o;4222:130::-;4276:7;4302:43;4321:23;;4302:14;;:18;;:43;;;;:::i;1443:28::-;;;;:::o;4507:1605::-;4553:7;4572:17;4592:19;:17;:19::i;:::-;4572:39;-1:-1:-1;4625:14:3;4621:28;;4648:1;4641:8;;;;;4621:28;4665:26;;;;;;;;;;;;;;;;;4702:16;4721:43;4757:6;4721:31;4735:16;;4721:9;:13;;:31;;;;:::i;:::-;:35;:43;:35;:43;:::i;:::-;4702:62;-1:-1:-1;4774:22:3;4799:23;:9;4702:62;4799:23;:13;:23;:::i;:::-;4850:14;;4774:48;;-1:-1:-1;4850:28:3;;4869:8;4850:28;:18;:28;:::i;:::-;4833:14;:45;4893:25;;;;;;;;;;;;;;;;;4931:16;;;;4928:40;;;4949:19;:17;:19::i;:::-;;4928:40;5022:5;;5044:14;;5022:41;;;-1:-1:-1;;;5022:41:3;;-1:-1:-1;;;;;5044:14:3;;;5022:41;;;;:5;:41;;;;;;;;:5;;;;;:13;;:41;;;;;;;;;;;;;;;;:5;:41;;;2:2:-1;;;;27:1;24;17:12;2:2;5022:41:3;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;5022:41:3;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;5022:41:3;5014:68;;;;;-1:-1:-1;;;5014:68:3;;;;;;;;;;;;-1:-1:-1;;;5014:68:3;;;;;;;;;;;;;;;5100:5;;5122:14;;5100:54;;;-1:-1:-1;;;5100:54:3;;-1:-1:-1;;;;;5122:14:3;;;5100:54;;;;;;;;;;;;:5;;;;;:13;;:54;;;;;;;;;;;;;;:5;;:54;;;2:2:-1;;;;27:1;24;17:12;2:2;5100:54:3;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;5100:54:3;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;5100:54:3;5092:81;;;;;-1:-1:-1;;;5092:81:3;;;;;;;;;;;;-1:-1:-1;;;5092:81:3;;;;;;;;;;;;;;;5203:14;;:30;;;-1:-1:-1;;;5203:30:3;;;;5183:17;;-1:-1:-1;;;;;5203:14:3;;:28;;:30;;;;;;;;;;;;;;:14;:30;;;2:2:-1;;;;27:1;24;17:12;2:2;5203:30:3;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;5203:30:3;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;5203:30:3;;-1:-1:-1;;;;;;;;;;5435:24:3;;;5432:328;;;5475:14;;5510:5;;-1:-1:-1;;;;;5475:14:3;;;;:26;;5510:5;5518:18;:16;:18::i;:::-;5538:14;5475:78;;;;;;;;;;;;;-1:-1:-1;;;;;5475:78:3;-1:-1:-1;;;;;5475:78:3;;;;;;-1:-1:-1;;;;;5475:78:3;-1:-1:-1;;;;;5475:78:3;;;;;;;;;;;;;;;;;;;;;;;;;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;5475:78:3;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;5475:78:3;;;;5432:328;;;-1:-1:-1;;;;;;;;;5581:24:3;;;5578:182;;;5620:14;;-1:-1:-1;;;;;5620:14:3;:26;5647:18;:16;:18::i;:::-;5667:14;5620:62;;;;;;;;;;;;;-1:-1:-1;;;;;5620:62:3;-1:-1:-1;;;;;5620:62:3;;;;;;;;;;;;;;;;;;;;;;;;;;5:9:-1;2:2;;;27:1;24;17:12;5578:182:3;5720:29;;;-1:-1:-1;;;5720:29:3;;;;;;;;;;;;-1:-1:-1;;;5720:29:3;;;;;;;;;;;;;;5578:182;5824:19;:17;:19::i;:::-;:24;5816:52;;;;;-1:-1:-1;;;5816:52:3;;;;;;;;;;;;-1:-1:-1;;;5816:52:3;;;;;;;;;;;;;;;5894:13;;:33;;5912:14;5894:33;:17;:33;:::i;:::-;5878:13;:49;5958:43;;;22:32:-1;6:49;;5958:43:3;;;;;;49:4:-1;25:18;;61:17;;-1:-1;;;;;182:15;-1:-1;;;179:29;160:49;;6011:3:3;;-1:-1:-1;;;;;6011:3:3;:24;6036:18;:16;:18::i;:::-;6062:16;;6011:68;;-1:-1:-1;;;;;;6011:68:3;;;;;;;-1:-1:-1;;;;;6011:68:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;6056:4;;6062:16;6011:68;;;;;;;;;;-1:-1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;6011:68:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;6011:68:3;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;6011:68:3;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;6096:9:3;;-1:-1:-1;;;;;;4507:1605:3;:::o;3380:195::-;3491:21;;3514:20;;3429:7;;3455:113;;-1:-1:-1;;;;;3491:21:3;;;;3514:20;3560:4;3455:35;:113::i;2499:104::-;2576:5;;-1:-1:-1;;;;;2576:5:3;2568:28;;2499:104;:::o;4060:156::-;4152:4;4168:20;:18;:20::i;:::-;-1:-1:-1;4205:4:3;;4060:156;-1:-1:-1;;;;;;4060:156:3:o;1123:226:12:-;1188:12;;-1:-1:-1;;;;;1188:12:12;1174:10;:26;1166:55;;;;;-1:-1:-1;;;1166:55:12;;;;;;;;;;;;-1:-1:-1;;;1166:55:12;;;;;;;;;;;;;;;1264:12;;;1257:5;;1236:41;;-1:-1:-1;;;;;1264:12:12;;;;1257:5;;;;1236:41;;;1295:12;;;;1287:20;;-1:-1:-1;;;;;;1287:20:12;;;-1:-1:-1;;;;;1295:12:12;;1287:20;;;;1317:25;;;1123:226::o;1171:62:3:-;1229:1;1171:62;:::o;1057:29::-;;;;:::o;756:31::-;;;;:::o;2783:213::-;739:5:12;;-1:-1:-1;;;;;739:5:12;725:10;:19;717:41;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;;;;2875:7:3::1;2860:11;:22;;2852:49;;;::::0;;-1:-1:-1;;;2852:49:3;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;2852:49:3;;;;;;;;;;;;;::::1;;2911:16;:30:::0;;;2956:33:::1;::::0;;;;;;;::::1;::::0;;;;::::1;::::0;;::::1;2783:213:::0;:::o;236:20:12:-;;;-1:-1:-1;;;;;236:20:12;;:::o;3114:260:3:-;3186:17;3273:5;;;;;;;;;-1:-1:-1;;;;;3273:5:3;3280:6;3206:81;;;;;;-1:-1:-1;;;;;3206:81:3;-1:-1:-1;;;;;3206:81:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;-1:-1;;3206:81:3;;;;;;;26:21:-1;;;-1:-1;;22:32;6:49;;3206:81:3;;-1:-1:-1;;49:4;25:18;;61:17;;-1:-1;;;;;;;;182:15;;;179:29;160:49;;3297:3:3;;3322:20;;;3350:16;;3297:70;;-1:-1:-1;;;3297:70:3;;-1:-1:-1;;;;;3322:20:3;;;3297:70;;;;;;;;;;;;;;;;;;;;;;;;;;;3206:81;;-1:-1:-1;3297:3:3;;;;;-1:-1:-1;3297:24:3;;-1:-1:-1;3322:20:3;;-1:-1:-1;3206:81:3;;-1:-1:-1;3297:70:3;;-1:-1:-1;3297:70:3;;;;;;;25:18:-1;3297:70:3;;25:18:-1;-1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;3297:70:3;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;3297:70:3;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;3297:70:3;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;;;;3114:260:3:o;1136:28::-;;;;;;:::o;978:36::-;;;-1:-1:-1;;;;;978:36:3;;:::o;1092:38::-;;;;:::o;673:36::-;;;-1:-1:-1;;;;;673:36:3;;:::o;1020:31::-;;;;:::o;715:35::-;;;-1:-1:-1;;;;;715:35:3;;:::o;262:27:12:-;;;-1:-1:-1;;;;;262:27:12;;:::o;929:102::-;739:5;;-1:-1:-1;;;;;739:5:12;725:10;:19;717:41;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;;;;1001:12:::1;:23:::0;;-1:-1:-1;;;;;;1001:23:12::1;-1:-1:-1::0;;;;;1001:23:12;;;::::1;::::0;;;::::1;::::0;;929:102::o;1528:965:3:-;1844:15;:13;:15::i;:::-;1843:16;1835:38;;;;;-1:-1:-1;;;1835:38:3;;;;;;;;;;;;-1:-1:-1;;;1835:38:3;;;;;;;;;;;;;;;1956:16;:23;;-1:-1:-1;;1956:23:3;1975:4;1956:23;;;:16;2034:18;;-1:-1:-1;;;;;;2034:18:3;;;2042:10;2034:18;;;;2063:14;:48;;-1:-1:-1;;;;;2063:48:3;;;;;;;;;;;;2132:31;;;-1:-1:-1;;;2132:31:3;;;;:14;;;;;:29;;:31;;;;;;;;;;;;;;:14;:31;;;2:2:-1;;;;27:1;24;17:12;2:2;2132:31:3;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;2132:31:3;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;2132:31:3;2121:3;:43;;-1:-1:-1;;;;;;2121:43:3;;;-1:-1:-1;;;;;2121:43:3;;;;;;;2174:5;:21;;;;;;;;;;2205:20;:44;;;;;;;;;;2259:16;:36;;;2305:21;:46;;;;;;;;;;;;;;2361:30;2373:17;2361:11;:30::i;:::-;2434:5;:14;;-1:-1:-1;;;;;;2434:14:3;-1:-1:-1;;;;;2434:14:3;;;;;2458:28;2479:6;2458:20;:28::i;:::-;1528:965;;;;;;;;:::o;793:18::-;;;-1:-1:-1;;;;;793:18:3;;:::o;874:176:17:-;932:7;963:5;;;986:6;;;;978:46;;;;;-1:-1:-1;;;978:46:17;;;;;;;;;;;;;;;;;;;;;;;;;;;;1042:1;-1:-1:-1;874:176:17;;;;;:::o;1321:134::-;1379:7;1405:43;1409:1;1412;1405:43;;;;;;;;;;;;;;;;;:3;:43::i;2180:459::-;2238:7;2479:6;2475:45;;-1:-1:-1;2508:1:17;2501:8;;2475:45;2542:5;;;2546:1;2542;:5;:1;2565:5;;;;;:10;2557:56;;;;-1:-1:-1;;;2557:56:17;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3101:130;3159:7;3185:39;3189:1;3192;3185:39;;;;;;;;;;;;;;;;;:3;:39::i;1371:393:0:-;1510:13;1535:16;1564:23;1578:8;1564:13;:23::i;:::-;1554:34;;;;;;;1639:114;;;-1:-1:-1;;;;;;1639:114:0;;;;-1:-1:-1;;1639:114:0;;;;;;;;;;;;;;;;;;;;;;;;;26:21:-1;;;22:32;;;6:49;;1639:114:0;;;;1629:125;;;;;;-1:-1:-1;;1371:393:0;;;;;:::o;1746:187:17:-;1832:7;1867:12;1859:6;;;;1851:29;;;;-1:-1:-1;;;1851:29:17;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;1851:29:17;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;1902:5:17;;;1746:187::o;3713:272::-;3799:7;3833:12;3826:5;3818:28;;;;-1:-1:-1;;;3818:28:17;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;3818:28:17;;3856:9;3872:1;3868;:5;;;;;;;3713:272;-1:-1:-1;;;;;3713:272:17:o;504:712:0:-;683:4;677:11;;724:4;714:15;;701:29;;;840:4;827:18;;-1:-1:-1;;;973:4:0;963:15;;956:91;568:17;619;;;;1077:4;1067:15;;1060:36;-1:-1:-1;;;1126:4:0;1116:15;;1109:91;677:11;655:555::o", + "source": "pragma solidity 0.6.6;\n\nimport \"openzeppelin-solidity/contracts/token/ERC20/ERC20.sol\";\nimport \"openzeppelin-solidity/contracts/math/SafeMath.sol\";\nimport \"./Ownable.sol\"; // TODO: switch to \"openzeppelin-solidity/contracts/access/Ownable.sol\";\nimport \"./PurchaseListener.sol\";\nimport \"./CloneLib.sol\";\nimport \"./IAMB.sol\";\nimport \"./ITokenMediator.sol\";\n\ncontract DataUnionMainnet is Ownable, PurchaseListener {\n using SafeMath for uint256;\n\n event AdminFeeChanged(uint256 adminFee);\n event AdminFeeCharged(uint256 amount);\n event AdminFeesWithdrawn(address indexed admin, uint256 amount);\n\n event RevenueReceived(uint256 amount);\n\n IAMB public amb;\n ITokenMediator public token_mediator;\n address public sidechain_DU_factory;\n uint256 public sidechain_maxgas;\n ERC20 public token;\n\n/*\n NOTE: any variables set below will NOT be visible in clones\n clones must set variables in initialize()\n*/\n\n // needed to compute sidechain address\n address public sidechain_template_DU;\n uint256 public adminFeeFraction;\n uint256 public totalAdminFees;\n uint256 public totalAdminFeesWithdrawn;\n bool public autoSendAdminFee;\n\n function version() public pure returns (uint256) { return 2; }\n\n /*\n totalEarnings includes:\n member earnings (ie revenue - admin fees)\n tokens held for members via transferToMemberInContract()\n\n totalRevenue = totalEarnings + totalAdminFees;\n*/\n uint256 public totalEarnings;\n\n\n constructor() public Ownable(address(0)) {}\n\n function initialize(\n address _token,\n address _token_mediator,\n address _sidechain_DU_factory,\n uint256 _sidechain_maxgas,\n address _sidechain_template_DU,\n address _owner,\n uint256 _adminFeeFraction,\n address[] memory agents\n ) public {\n require(!isInitialized(), \"init_once\");\n // must set default values here so that there are in clone state\n autoSendAdminFee = true;\n\n //during setup, msg.sender is admin\n owner = msg.sender;\n\n token_mediator = ITokenMediator(_token_mediator);\n amb = IAMB(token_mediator.bridgeContract());\n token = ERC20(_token);\n sidechain_DU_factory = _sidechain_DU_factory;\n sidechain_maxgas = _sidechain_maxgas;\n sidechain_template_DU = _sidechain_template_DU;\n setAdminFee(_adminFeeFraction);\n //transfer to real admin\n owner = _owner;\n deployNewDUSidechain(agents);\n }\n\n function isInitialized() public view returns (bool) {\n return address(token) != address(0);\n }\n\n /**\n * Admin fee as a fraction of revenue.\n * @param newAdminFee fixed-point decimal in the same way as ether: 50% === 0.5 ether === \"500000000000000000\"\n */\n function setAdminFee(uint256 newAdminFee) public onlyOwner {\n require(newAdminFee <= 1 ether, \"error_adminFee\");\n adminFeeFraction = newAdminFee;\n emit AdminFeeChanged(adminFeeFraction);\n }\n\n function setAutoSendAdminFee(bool autoSend) public onlyOwner {\n autoSendAdminFee = autoSend;\n }\n\n\n function deployNewDUSidechain(address[] memory agents) public {\n bytes memory data = abi.encodeWithSignature(\"deployNewDUSidechain(address,address[])\", owner, agents);\n amb.requireToPassMessage(sidechain_DU_factory, data, sidechain_maxgas);\n }\n\n function sidechainAddress() public view returns (address) {\n return CloneLib.predictCloneAddressCreate2(sidechain_template_DU, sidechain_DU_factory, bytes32(uint256(address(this))));\n }\n\n/*\n2 way doesnt work atm\n //calls withdraw(member) on home network\n function withdraw(address member) public {\n bytes memory data = abi.encodeWithSignature(\n \"withdraw(address,bool)\",\n member,\n true\n );\n amb.requireToPassMessage(sidechainAddress(), data, sidechain_maxgas);\n }\n */\n\n //function onPurchase(bytes32 productId, address subscriber, uint256 endTimestamp, uint256 priceDatacoin, uint256 feeDatacoin)\n function onPurchase(bytes32, address, uint256, uint256, uint256) external override returns (bool) {\n sendTokensToBridge();\n return true;\n }\n\n function adminFeesWithdrawable() public view returns (uint256) {\n return totalAdminFees.sub(totalAdminFeesWithdrawn);\n }\n\n function unaccountedTokens() public view returns (uint256) {\n return token.balanceOf(address(this)).sub(adminFeesWithdrawable());\n }\n\n\n function sendTokensToBridge() public returns (uint256) {\n uint256 newTokens = unaccountedTokens();\n if (newTokens == 0) return 0;\n\n emit RevenueReceived(newTokens);\n\n uint256 adminFee = newTokens.mul(adminFeeFraction).div(10**18);\n uint256 memberEarnings = newTokens.sub(adminFee);\n\n totalAdminFees = totalAdminFees.add(adminFee);\n emit AdminFeeCharged(adminFee);\n if(autoSendAdminFee) withdrawAdminFees();\n\n // transfer memberEarnings\n require(token.approve(address(token_mediator), 0), \"approve_failed\");\n require(token.approve(address(token_mediator), memberEarnings), \"approve_failed\");\n bytes4 bridgeMode = token_mediator.getBridgeMode();\n //MultiAMB 0xb1516c26 == bytes4(keccak256(abi.encodePacked(\"multi-erc-to-erc-amb\")))\n //Single token AMB 0x76595b56 == bytes4(keccak256(abi.encodePacked(\"erc-to-erc-amb\")))\n if(bridgeMode == 0xb1516c26) {\n token_mediator.relayTokens(address(token), sidechainAddress(), memberEarnings);\n }\n else if(bridgeMode == 0x76595b56){\n token_mediator.relayTokens(sidechainAddress(), memberEarnings);\n }\n else{\n revert(\"unknown_bridge_mode\");\n }\n\n //check that memberEarnings were sent\n require(unaccountedTokens() == 0, \"not_transferred\");\n totalEarnings = totalEarnings.add(memberEarnings);\n\n bytes memory data = abi.encodeWithSignature(\"refreshRevenue()\");\n amb.requireToPassMessage(sidechainAddress(), data, sidechain_maxgas);\n return newTokens;\n }\n\n function withdrawAdminFees() public returns (uint256) {\n uint256 withdrawable = adminFeesWithdrawable();\n if (withdrawable == 0) return 0;\n totalAdminFeesWithdrawn = totalAdminFeesWithdrawn.add(withdrawable);\n require(token.transfer(owner, withdrawable), \"transfer_failed\");\n emit AdminFeesWithdrawn(owner, withdrawable);\n return withdrawable;\n }\n}\n", + "sourcePath": "/home/heynow/streamr/data-union-solidity/contracts/DataUnionMainnet.sol", + "ast": { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/DataUnionMainnet.sol", + "exportedSymbols": { + "DataUnionMainnet": [ + 1165 + ] + }, + "id": 1166, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 668, + "literals": [ + "solidity", + "0.6", + ".6" + ], + "nodeType": "PragmaDirective", + "src": "0:22:3" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol", + "file": "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol", + "id": 669, + "nodeType": "ImportDirective", + "scope": 1166, + "sourceUnit": 4149, + "src": "24:63:3", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "file": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "id": 670, + "nodeType": "ImportDirective", + "scope": 1166, + "sourceUnit": 3642, + "src": "88:59:3", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/Ownable.sol", + "file": "./Ownable.sol", + "id": 671, + "nodeType": "ImportDirective", + "scope": 1166, + "sourceUnit": 3181, + "src": "148:23:3", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/PurchaseListener.sol", + "file": "./PurchaseListener.sol", + "id": 672, + "nodeType": "ImportDirective", + "scope": 1166, + "sourceUnit": 3200, + "src": "245:32:3", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/CloneLib.sol", + "file": "./CloneLib.sol", + "id": 673, + "nodeType": "ImportDirective", + "scope": 1166, + "sourceUnit": 155, + "src": "278:24:3", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/IAMB.sol", + "file": "./IAMB.sol", + "id": 674, + "nodeType": "ImportDirective", + "scope": 1166, + "sourceUnit": 2913, + "src": "303:20:3", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/ITokenMediator.sol", + "file": "./ITokenMediator.sol", + "id": 675, + "nodeType": "ImportDirective", + "scope": 1166, + "sourceUnit": 2969, + "src": "324:30:3", + "symbolAliases": [], + "unitAlias": "" + }, + { + "abstract": false, + "baseContracts": [ + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 676, + "name": "Ownable", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 3180, + "src": "385:7:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_Ownable_$3180", + "typeString": "contract Ownable" + } + }, + "id": 677, + "nodeType": "InheritanceSpecifier", + "src": "385:7:3" + }, + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 678, + "name": "PurchaseListener", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 3199, + "src": "394:16:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_PurchaseListener_$3199", + "typeString": "contract PurchaseListener" + } + }, + "id": 679, + "nodeType": "InheritanceSpecifier", + "src": "394:16:3" + } + ], + "contractDependencies": [ + 3180, + 3199 + ], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 1165, + "linearizedBaseContracts": [ + 1165, + 3199, + 3180 + ], + "name": "DataUnionMainnet", + "nodeType": "ContractDefinition", + "nodes": [ + { + "id": 682, + "libraryName": { + "contractScope": null, + "id": 680, + "name": "SafeMath", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 3641, + "src": "423:8:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_SafeMath_$3641", + "typeString": "library SafeMath" + } + }, + "nodeType": "UsingForDirective", + "src": "417:27:3", + "typeName": { + "id": 681, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "436:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + }, + { + "anonymous": false, + "documentation": null, + "id": 686, + "name": "AdminFeeChanged", + "nodeType": "EventDefinition", + "parameters": { + "id": 685, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 684, + "indexed": false, + "mutability": "mutable", + "name": "adminFee", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 686, + "src": "472:16:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 683, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "472:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "471:18:3" + }, + "src": "450:40:3" + }, + { + "anonymous": false, + "documentation": null, + "id": 690, + "name": "AdminFeeCharged", + "nodeType": "EventDefinition", + "parameters": { + "id": 689, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 688, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 690, + "src": "517:14:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 687, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "517:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "516:16:3" + }, + "src": "495:38:3" + }, + { + "anonymous": false, + "documentation": null, + "id": 696, + "name": "AdminFeesWithdrawn", + "nodeType": "EventDefinition", + "parameters": { + "id": 695, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 692, + "indexed": true, + "mutability": "mutable", + "name": "admin", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 696, + "src": "563:21:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 691, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "563:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 694, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 696, + "src": "586:14:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 693, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "586:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "562:39:3" + }, + "src": "538:64:3" + }, + { + "anonymous": false, + "documentation": null, + "id": 700, + "name": "RevenueReceived", + "nodeType": "EventDefinition", + "parameters": { + "id": 699, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 698, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 700, + "src": "630:14:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 697, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "630:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "629:16:3" + }, + "src": "608:38:3" + }, + { + "constant": false, + "functionSelector": "1062b39a", + "id": 702, + "mutability": "mutable", + "name": "amb", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "652:15:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + }, + "typeName": { + "contractScope": null, + "id": 701, + "name": "IAMB", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 2912, + "src": "652:4:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "c5a8c91f", + "id": 704, + "mutability": "mutable", + "name": "token_mediator", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "673:36:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + }, + "typeName": { + "contractScope": null, + "id": 703, + "name": "ITokenMediator", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 2968, + "src": "673:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "e22ec112", + "id": 706, + "mutability": "mutable", + "name": "sidechain_DU_factory", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "715:35:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 705, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "715:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "692199d4", + "id": 708, + "mutability": "mutable", + "name": "sidechain_maxgas", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "756:31:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 707, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "756:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "fc0c546a", + "id": 710, + "mutability": "mutable", + "name": "token", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "793:18:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + }, + "typeName": { + "contractScope": null, + "id": 709, + "name": "ERC20", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 4148, + "src": "793:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "a65c87d9", + "id": 712, + "mutability": "mutable", + "name": "sidechain_template_DU", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "978:36:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 711, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "978:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "d35cec40", + "id": 714, + "mutability": "mutable", + "name": "adminFeeFraction", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "1020:31:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 713, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1020:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "61feacff", + "id": 716, + "mutability": "mutable", + "name": "totalAdminFees", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "1057:29:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 715, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1057:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "a913f41d", + "id": 718, + "mutability": "mutable", + "name": "totalAdminFeesWithdrawn", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "1092:38:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 717, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1092:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "9eeba07c", + "id": 720, + "mutability": "mutable", + "name": "autoSendAdminFee", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "1136:28:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 719, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "1136:4:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "public" + }, + { + "body": { + "id": 727, + "nodeType": "Block", + "src": "1220:13:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "hexValue": "32", + "id": 725, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1229:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_2_by_1", + "typeString": "int_const 2" + }, + "value": "2" + }, + "functionReturnParameters": 724, + "id": 726, + "nodeType": "Return", + "src": "1222:8:3" + } + ] + }, + "documentation": null, + "functionSelector": "54fd4d50", + "id": 728, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "version", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 721, + "nodeType": "ParameterList", + "parameters": [], + "src": "1187:2:3" + }, + "returnParameters": { + "id": 724, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 723, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 728, + "src": "1211:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 722, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1211:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1210:9:3" + }, + "scope": 1165, + "src": "1171:62:3", + "stateMutability": "pure", + "virtual": false, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "2df3eba4", + "id": 730, + "mutability": "mutable", + "name": "totalEarnings", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "1443:28:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 729, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1443:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "body": { + "id": 739, + "nodeType": "Block", + "src": "1520:2:3", + "statements": [] + }, + "documentation": null, + "id": 740, + "implemented": true, + "kind": "constructor", + "modifiers": [ + { + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "30", + "id": 735, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1516:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "id": 734, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "1508:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 733, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1508:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 736, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1508:10:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "id": 737, + "modifierName": { + "argumentTypes": null, + "id": 732, + "name": "Ownable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3180, + "src": "1500:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_Ownable_$3180_$", + "typeString": "type(contract Ownable)" + } + }, + "nodeType": "ModifierInvocation", + "src": "1500:19:3" + } + ], + "name": "", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 731, + "nodeType": "ParameterList", + "parameters": [], + "src": "1490:2:3" + }, + "returnParameters": { + "id": 738, + "nodeType": "ParameterList", + "parameters": [], + "src": "1520:0:3" + }, + "scope": 1165, + "src": "1479:43:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 820, + "nodeType": "Block", + "src": "1825:668:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 763, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "!", + "prefix": true, + "src": "1843:16:3", + "subExpression": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 761, + "name": "isInitialized", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 837, + "src": "1844:13:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_bool_$", + "typeString": "function () view returns (bool)" + } + }, + "id": 762, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1844:15:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "696e69745f6f6e6365", + "id": 764, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1861:11:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_489583184b3bb72a2d8c2cf7b5f4c46635517588498db21bc763656f9e5ab21e", + "typeString": "literal_string \"init_once\"" + }, + "value": "init_once" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_489583184b3bb72a2d8c2cf7b5f4c46635517588498db21bc763656f9e5ab21e", + "typeString": "literal_string \"init_once\"" + } + ], + "id": 760, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "1835:7:3", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 765, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1835:38:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 766, + "nodeType": "ExpressionStatement", + "src": "1835:38:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 769, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 767, + "name": "autoSendAdminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 720, + "src": "1956:16:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "hexValue": "74727565", + "id": 768, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1975:4:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "src": "1956:23:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 770, + "nodeType": "ExpressionStatement", + "src": "1956:23:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 774, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 771, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "2034:5:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 772, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "2042:3:3", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 773, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "2042:10:3", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "src": "2034:18:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 775, + "nodeType": "ExpressionStatement", + "src": "2034:18:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 780, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 776, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 704, + "src": "2063:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 778, + "name": "_token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 744, + "src": "2095:15:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 777, + "name": "ITokenMediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2968, + "src": "2080:14:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_ITokenMediator_$2968_$", + "typeString": "type(contract ITokenMediator)" + } + }, + "id": 779, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2080:31:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "src": "2063:48:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 781, + "nodeType": "ExpressionStatement", + "src": "2063:48:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 788, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 782, + "name": "amb", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 702, + "src": "2121:3:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "expression": { + "argumentTypes": null, + "id": 784, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 704, + "src": "2132:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 785, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "bridgeContract", + "nodeType": "MemberAccess", + "referencedDeclaration": 2946, + "src": "2132:29:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$__$returns$_t_address_$", + "typeString": "function () view external returns (address)" + } + }, + "id": 786, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2132:31:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 783, + "name": "IAMB", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2912, + "src": "2127:4:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_IAMB_$2912_$", + "typeString": "type(contract IAMB)" + } + }, + "id": 787, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2127:37:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "src": "2121:43:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "id": 789, + "nodeType": "ExpressionStatement", + "src": "2121:43:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 794, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 790, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 710, + "src": "2174:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 792, + "name": "_token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 742, + "src": "2188:6:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 791, + "name": "ERC20", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 4148, + "src": "2182:5:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_ERC20_$4148_$", + "typeString": "type(contract ERC20)" + } + }, + "id": 793, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2182:13:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "src": "2174:21:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "id": 795, + "nodeType": "ExpressionStatement", + "src": "2174:21:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 798, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 796, + "name": "sidechain_DU_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 706, + "src": "2205:20:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 797, + "name": "_sidechain_DU_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 746, + "src": "2228:21:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "2205:44:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 799, + "nodeType": "ExpressionStatement", + "src": "2205:44:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 802, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 800, + "name": "sidechain_maxgas", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 708, + "src": "2259:16:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 801, + "name": "_sidechain_maxgas", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 748, + "src": "2278:17:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "2259:36:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 803, + "nodeType": "ExpressionStatement", + "src": "2259:36:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 806, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 804, + "name": "sidechain_template_DU", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 712, + "src": "2305:21:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 805, + "name": "_sidechain_template_DU", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 750, + "src": "2329:22:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "2305:46:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 807, + "nodeType": "ExpressionStatement", + "src": "2305:46:3" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 809, + "name": "_adminFeeFraction", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 754, + "src": "2373:17:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 808, + "name": "setAdminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 861, + "src": "2361:11:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 810, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2361:30:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 811, + "nodeType": "ExpressionStatement", + "src": "2361:30:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 814, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 812, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "2434:5:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 813, + "name": "_owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 752, + "src": "2442:6:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "2434:14:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 815, + "nodeType": "ExpressionStatement", + "src": "2434:14:3" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 817, + "name": "agents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 757, + "src": "2479:6:3", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + ], + "id": 816, + "name": "deployNewDUSidechain", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 897, + "src": "2458:20:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_array$_t_address_$dyn_memory_ptr_$returns$__$", + "typeString": "function (address[] memory)" + } + }, + "id": 818, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2458:28:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 819, + "nodeType": "ExpressionStatement", + "src": "2458:28:3" + } + ] + }, + "documentation": null, + "functionSelector": "fb6470c9", + "id": 821, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "initialize", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 758, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 742, + "mutability": "mutable", + "name": "_token", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1557:14:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 741, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1557:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 744, + "mutability": "mutable", + "name": "_token_mediator", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1581:23:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 743, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1581:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 746, + "mutability": "mutable", + "name": "_sidechain_DU_factory", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1614:29:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 745, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1614:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 748, + "mutability": "mutable", + "name": "_sidechain_maxgas", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1653:25:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 747, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1653:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 750, + "mutability": "mutable", + "name": "_sidechain_template_DU", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1688:30:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 749, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1688:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 752, + "mutability": "mutable", + "name": "_owner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1728:14:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 751, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1728:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 754, + "mutability": "mutable", + "name": "_adminFeeFraction", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1752:25:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 753, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1752:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 757, + "mutability": "mutable", + "name": "agents", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1787:23:3", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 755, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1787:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 756, + "length": null, + "nodeType": "ArrayTypeName", + "src": "1787:9:3", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1547:269:3" + }, + "returnParameters": { + "id": 759, + "nodeType": "ParameterList", + "parameters": [], + "src": "1825:0:3" + }, + "scope": 1165, + "src": "1528:965:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 836, + "nodeType": "Block", + "src": "2551:52:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 834, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 828, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 710, + "src": "2576:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + ], + "id": 827, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2568:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 826, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2568:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 829, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2568:14:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "30", + "id": 832, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2594:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "id": 831, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2586:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 830, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2586:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 833, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2586:10:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "src": "2568:28:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "functionReturnParameters": 825, + "id": 835, + "nodeType": "Return", + "src": "2561:35:3" + } + ] + }, + "documentation": null, + "functionSelector": "392e53cd", + "id": 837, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "isInitialized", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 822, + "nodeType": "ParameterList", + "parameters": [], + "src": "2521:2:3" + }, + "returnParameters": { + "id": 825, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 824, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 837, + "src": "2545:4:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 823, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "2545:4:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2544:6:3" + }, + "scope": 1165, + "src": "2499:104:3", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 860, + "nodeType": "Block", + "src": "2842:154:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 848, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 846, + "name": "newAdminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 840, + "src": "2860:11:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "<=", + "rightExpression": { + "argumentTypes": null, + "hexValue": "31", + "id": 847, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2875:7:3", + "subdenomination": "ether", + "typeDescriptions": { + "typeIdentifier": "t_rational_1000000000000000000_by_1", + "typeString": "int_const 1000000000000000000" + }, + "value": "1" + }, + "src": "2860:22:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f61646d696e466565", + "id": 849, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2884:16:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_c137fd2ee13f1c30c5cc71b65e2c7f49f5120b580d70d8298b6185b09e6c1e40", + "typeString": "literal_string \"error_adminFee\"" + }, + "value": "error_adminFee" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_c137fd2ee13f1c30c5cc71b65e2c7f49f5120b580d70d8298b6185b09e6c1e40", + "typeString": "literal_string \"error_adminFee\"" + } + ], + "id": 845, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "2852:7:3", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 850, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2852:49:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 851, + "nodeType": "ExpressionStatement", + "src": "2852:49:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 854, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 852, + "name": "adminFeeFraction", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 714, + "src": "2911:16:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 853, + "name": "newAdminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 840, + "src": "2930:11:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "2911:30:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 855, + "nodeType": "ExpressionStatement", + "src": "2911:30:3" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 857, + "name": "adminFeeFraction", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 714, + "src": "2972:16:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 856, + "name": "AdminFeeChanged", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 686, + "src": "2956:15:3", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 858, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2956:33:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 859, + "nodeType": "EmitStatement", + "src": "2951:38:3" + } + ] + }, + "documentation": { + "id": 838, + "nodeType": "StructuredDocumentation", + "src": "2609:169:3", + "text": "Admin fee as a fraction of revenue.\n@param newAdminFee fixed-point decimal in the same way as ether: 50% === 0.5 ether === \"500000000000000000\"" + }, + "functionSelector": "8beb60b6", + "id": 861, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 843, + "modifierName": { + "argumentTypes": null, + "id": 842, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "2832:9:3", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "2832:9:3" + } + ], + "name": "setAdminFee", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 841, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 840, + "mutability": "mutable", + "name": "newAdminFee", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 861, + "src": "2804:19:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 839, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "2804:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2803:21:3" + }, + "returnParameters": { + "id": 844, + "nodeType": "ParameterList", + "parameters": [], + "src": "2842:0:3" + }, + "scope": 1165, + "src": "2783:213:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 872, + "nodeType": "Block", + "src": "3063:44:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 870, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 868, + "name": "autoSendAdminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 720, + "src": "3073:16:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 869, + "name": "autoSend", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 863, + "src": "3092:8:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "3073:27:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 871, + "nodeType": "ExpressionStatement", + "src": "3073:27:3" + } + ] + }, + "documentation": null, + "functionSelector": "0f3afcbe", + "id": 873, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 866, + "modifierName": { + "argumentTypes": null, + "id": 865, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "3053:9:3", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "3053:9:3" + } + ], + "name": "setAutoSendAdminFee", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 864, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 863, + "mutability": "mutable", + "name": "autoSend", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 873, + "src": "3031:13:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 862, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "3031:4:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "3030:15:3" + }, + "returnParameters": { + "id": 867, + "nodeType": "ParameterList", + "parameters": [], + "src": "3063:0:3" + }, + "scope": 1165, + "src": "3002:105:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 896, + "nodeType": "Block", + "src": "3176:198:3", + "statements": [ + { + "assignments": [ + 880 + ], + "declarations": [ + { + "constant": false, + "id": 880, + "mutability": "mutable", + "name": "data", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 896, + "src": "3186:17:3", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 879, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "3186:5:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 887, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "6465706c6f794e6577445553696465636861696e28616464726573732c616464726573735b5d29", + "id": 883, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3230:41:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_325ff66f80d01e2243bd492c3b38e663416cd478dca0ff43243ddb11328dca3f", + "typeString": "literal_string \"deployNewDUSidechain(address,address[])\"" + }, + "value": "deployNewDUSidechain(address,address[])" + }, + { + "argumentTypes": null, + "id": 884, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "3273:5:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 885, + "name": "agents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 876, + "src": "3280:6:3", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_325ff66f80d01e2243bd492c3b38e663416cd478dca0ff43243ddb11328dca3f", + "typeString": "literal_string \"deployNewDUSidechain(address,address[])\"" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + ], + "expression": { + "argumentTypes": null, + "id": 881, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "3206:3:3", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 882, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encodeWithSignature", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "3206:23:3", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencodewithsignature_pure$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (string memory) pure returns (bytes memory)" + } + }, + "id": 886, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3206:81:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "3186:101:3" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 891, + "name": "sidechain_DU_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 706, + "src": "3322:20:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 892, + "name": "data", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 880, + "src": "3344:4:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "id": 893, + "name": "sidechain_maxgas", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 708, + "src": "3350:16:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 888, + "name": "amb", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 702, + "src": "3297:3:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "id": 890, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "requireToPassMessage", + "nodeType": "MemberAccess", + "referencedDeclaration": 2911, + "src": "3297:24:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_bytes_memory_ptr_$_t_uint256_$returns$_t_bytes32_$", + "typeString": "function (address,bytes memory,uint256) external returns (bytes32)" + } + }, + "id": 894, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3297:70:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "id": 895, + "nodeType": "ExpressionStatement", + "src": "3297:70:3" + } + ] + }, + "documentation": null, + "functionSelector": "99dd1c81", + "id": 897, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "deployNewDUSidechain", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 877, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 876, + "mutability": "mutable", + "name": "agents", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 897, + "src": "3144:23:3", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 874, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3144:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 875, + "length": null, + "nodeType": "ArrayTypeName", + "src": "3144:9:3", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "3143:25:3" + }, + "returnParameters": { + "id": 878, + "nodeType": "ParameterList", + "parameters": [], + "src": "3176:0:3" + }, + "scope": 1165, + "src": "3114:260:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 918, + "nodeType": "Block", + "src": "3438:137:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 904, + "name": "sidechain_template_DU", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 712, + "src": "3491:21:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 905, + "name": "sidechain_DU_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 706, + "src": "3514:20:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 912, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "3560:4:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionMainnet_$1165", + "typeString": "contract DataUnionMainnet" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionMainnet_$1165", + "typeString": "contract DataUnionMainnet" + } + ], + "id": 911, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "3552:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 910, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3552:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 913, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3552:13:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 909, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "3544:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_uint256_$", + "typeString": "type(uint256)" + }, + "typeName": { + "id": 908, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "3544:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 914, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3544:22:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 907, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "3536:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes32_$", + "typeString": "type(bytes32)" + }, + "typeName": { + "id": 906, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "3536:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 915, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3536:31:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "expression": { + "argumentTypes": null, + "id": 902, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "3455:8:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 903, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "predictCloneAddressCreate2", + "nodeType": "MemberAccess", + "referencedDeclaration": 61, + "src": "3455:35:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_address_$_t_address_$_t_bytes32_$returns$_t_address_$", + "typeString": "function (address,address,bytes32) pure returns (address)" + } + }, + "id": 916, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3455:113:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "functionReturnParameters": 901, + "id": 917, + "nodeType": "Return", + "src": "3448:120:3" + } + ] + }, + "documentation": null, + "functionSelector": "37b43a94", + "id": 919, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "sidechainAddress", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 898, + "nodeType": "ParameterList", + "parameters": [], + "src": "3405:2:3" + }, + "returnParameters": { + "id": 901, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 900, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 919, + "src": "3429:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 899, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3429:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "3428:9:3" + }, + "scope": 1165, + "src": "3380:195:3", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "baseFunctions": [ + 3198 + ], + "body": { + "id": 940, + "nodeType": "Block", + "src": "4158:58:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 935, + "name": "sendTokensToBridge", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1124, + "src": "4168:18:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$__$returns$_t_uint256_$", + "typeString": "function () returns (uint256)" + } + }, + "id": 936, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4168:20:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 937, + "nodeType": "ExpressionStatement", + "src": "4168:20:3" + }, + { + "expression": { + "argumentTypes": null, + "hexValue": "74727565", + "id": 938, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4205:4:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "functionReturnParameters": 934, + "id": 939, + "nodeType": "Return", + "src": "4198:11:3" + } + ] + }, + "documentation": null, + "functionSelector": "4a439cc0", + "id": 941, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "onPurchase", + "nodeType": "FunctionDefinition", + "overrides": { + "id": 931, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "4134:8:3" + }, + "parameters": { + "id": 930, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 921, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 941, + "src": "4080:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 920, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "4080:7:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 923, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 941, + "src": "4089:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 922, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4089:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 925, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 941, + "src": "4098:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 924, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4098:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 927, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 941, + "src": "4107:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 926, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4107:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 929, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 941, + "src": "4116:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 928, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4116:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4079:45:3" + }, + "returnParameters": { + "id": 934, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 933, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 941, + "src": "4152:4:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 932, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "4152:4:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4151:6:3" + }, + "scope": 1165, + "src": "4060:156:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + }, + { + "body": { + "id": 951, + "nodeType": "Block", + "src": "4285:67:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 948, + "name": "totalAdminFeesWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 718, + "src": "4321:23:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 946, + "name": "totalAdminFees", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 716, + "src": "4302:14:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 947, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "4302:18:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 949, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4302:43:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 945, + "id": 950, + "nodeType": "Return", + "src": "4295:50:3" + } + ] + }, + "documentation": null, + "functionSelector": "13fd3c56", + "id": 952, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "adminFeesWithdrawable", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 942, + "nodeType": "ParameterList", + "parameters": [], + "src": "4252:2:3" + }, + "returnParameters": { + "id": 945, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 944, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 952, + "src": "4276:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 943, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4276:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4275:9:3" + }, + "scope": 1165, + "src": "4222:130:3", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 969, + "nodeType": "Block", + "src": "4417:83:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 965, + "name": "adminFeesWithdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 952, + "src": "4469:21:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", + "typeString": "function () view returns (uint256)" + } + }, + "id": 966, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4469:23:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 961, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "4458:4:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionMainnet_$1165", + "typeString": "contract DataUnionMainnet" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionMainnet_$1165", + "typeString": "contract DataUnionMainnet" + } + ], + "id": 960, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "4450:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 959, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4450:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 962, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4450:13:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "expression": { + "argumentTypes": null, + "id": 957, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 710, + "src": "4434:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "id": 958, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "balanceOf", + "nodeType": "MemberAccess", + "referencedDeclaration": 3748, + "src": "4434:15:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view external returns (uint256)" + } + }, + "id": 963, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4434:30:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 964, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "4434:34:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 967, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4434:59:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 956, + "id": 968, + "nodeType": "Return", + "src": "4427:66:3" + } + ] + }, + "documentation": null, + "functionSelector": "132b4194", + "id": 970, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "unaccountedTokens", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 953, + "nodeType": "ParameterList", + "parameters": [], + "src": "4384:2:3" + }, + "returnParameters": { + "id": 956, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 955, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 970, + "src": "4408:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 954, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4408:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4407:9:3" + }, + "scope": 1165, + "src": "4358:142:3", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1123, + "nodeType": "Block", + "src": "4562:1550:3", + "statements": [ + { + "assignments": [ + 976 + ], + "declarations": [ + { + "constant": false, + "id": 976, + "mutability": "mutable", + "name": "newTokens", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1123, + "src": "4572:17:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 975, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4572:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 979, + "initialValue": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 977, + "name": "unaccountedTokens", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 970, + "src": "4592:17:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", + "typeString": "function () view returns (uint256)" + } + }, + "id": 978, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4592:19:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "4572:39:3" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 982, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 980, + "name": "newTokens", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 976, + "src": "4625:9:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 981, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4638:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "4625:14:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 985, + "nodeType": "IfStatement", + "src": "4621:28:3", + "trueBody": { + "expression": { + "argumentTypes": null, + "hexValue": "30", + "id": 983, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4648:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "functionReturnParameters": 974, + "id": 984, + "nodeType": "Return", + "src": "4641:8:3" + } + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 987, + "name": "newTokens", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 976, + "src": "4681:9:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 986, + "name": "RevenueReceived", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 700, + "src": "4665:15:3", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 988, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4665:26:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 989, + "nodeType": "EmitStatement", + "src": "4660:31:3" + }, + { + "assignments": [ + 991 + ], + "declarations": [ + { + "constant": false, + "id": 991, + "mutability": "mutable", + "name": "adminFee", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1123, + "src": "4702:16:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 990, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4702:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1001, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_rational_1000000000000000000_by_1", + "typeString": "int_const 1000000000000000000" + }, + "id": 999, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "hexValue": "3130", + "id": 997, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4757:2:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_10_by_1", + "typeString": "int_const 10" + }, + "value": "10" + }, + "nodeType": "BinaryOperation", + "operator": "**", + "rightExpression": { + "argumentTypes": null, + "hexValue": "3138", + "id": 998, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4761:2:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_18_by_1", + "typeString": "int_const 18" + }, + "value": "18" + }, + "src": "4757:6:3", + "typeDescriptions": { + "typeIdentifier": "t_rational_1000000000000000000_by_1", + "typeString": "int_const 1000000000000000000" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1000000000000000000_by_1", + "typeString": "int_const 1000000000000000000" + } + ], + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 994, + "name": "adminFeeFraction", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 714, + "src": "4735:16:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 992, + "name": "newTokens", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 976, + "src": "4721:9:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 993, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "mul", + "nodeType": "MemberAccess", + "referencedDeclaration": 3554, + "src": "4721:13:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 995, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4721:31:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 996, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "div", + "nodeType": "MemberAccess", + "referencedDeclaration": 3571, + "src": "4721:35:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1000, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4721:43:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "4702:62:3" + }, + { + "assignments": [ + 1003 + ], + "declarations": [ + { + "constant": false, + "id": 1003, + "mutability": "mutable", + "name": "memberEarnings", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1123, + "src": "4774:22:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1002, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4774:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1008, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1006, + "name": "adminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 991, + "src": "4813:8:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1004, + "name": "newTokens", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 976, + "src": "4799:9:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1005, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "4799:13:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1007, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4799:23:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "4774:48:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 1014, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1009, + "name": "totalAdminFees", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 716, + "src": "4833:14:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1012, + "name": "adminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 991, + "src": "4869:8:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1010, + "name": "totalAdminFees", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 716, + "src": "4850:14:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1011, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "4850:18:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1013, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4850:28:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "4833:45:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1015, + "nodeType": "ExpressionStatement", + "src": "4833:45:3" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1017, + "name": "adminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 991, + "src": "4909:8:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1016, + "name": "AdminFeeCharged", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 690, + "src": "4893:15:3", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 1018, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4893:25:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1019, + "nodeType": "EmitStatement", + "src": "4888:30:3" + }, + { + "condition": { + "argumentTypes": null, + "id": 1020, + "name": "autoSendAdminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 720, + "src": "4931:16:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 1024, + "nodeType": "IfStatement", + "src": "4928:40:3", + "trueBody": { + "expression": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1021, + "name": "withdrawAdminFees", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1164, + "src": "4949:17:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$__$returns$_t_uint256_$", + "typeString": "function () returns (uint256)" + } + }, + "id": 1022, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4949:19:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1023, + "nodeType": "ExpressionStatement", + "src": "4949:19:3" + } + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1030, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 704, + "src": "5044:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + ], + "id": 1029, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "5036:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1028, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "5036:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1031, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5036:23:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "hexValue": "30", + "id": 1032, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5061:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "expression": { + "argumentTypes": null, + "id": 1026, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 710, + "src": "5022:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "id": 1027, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "approve", + "nodeType": "MemberAccess", + "referencedDeclaration": 3808, + "src": "5022:13:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", + "typeString": "function (address,uint256) external returns (bool)" + } + }, + "id": 1033, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5022:41:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "617070726f76655f6661696c6564", + "id": 1034, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5065:16:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_0b99e75f7873826fa8b33e45f5babad412309f79417b9b0000257bd68fa2b8a3", + "typeString": "literal_string \"approve_failed\"" + }, + "value": "approve_failed" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_0b99e75f7873826fa8b33e45f5babad412309f79417b9b0000257bd68fa2b8a3", + "typeString": "literal_string \"approve_failed\"" + } + ], + "id": 1025, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "5014:7:3", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1035, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5014:68:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1036, + "nodeType": "ExpressionStatement", + "src": "5014:68:3" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1042, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 704, + "src": "5122:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + ], + "id": 1041, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "5114:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1040, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "5114:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1043, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5114:23:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1044, + "name": "memberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1003, + "src": "5139:14:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1038, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 710, + "src": "5100:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "id": 1039, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "approve", + "nodeType": "MemberAccess", + "referencedDeclaration": 3808, + "src": "5100:13:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", + "typeString": "function (address,uint256) external returns (bool)" + } + }, + "id": 1045, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5100:54:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "617070726f76655f6661696c6564", + "id": 1046, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5156:16:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_0b99e75f7873826fa8b33e45f5babad412309f79417b9b0000257bd68fa2b8a3", + "typeString": "literal_string \"approve_failed\"" + }, + "value": "approve_failed" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_0b99e75f7873826fa8b33e45f5babad412309f79417b9b0000257bd68fa2b8a3", + "typeString": "literal_string \"approve_failed\"" + } + ], + "id": 1037, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "5092:7:3", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1047, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5092:81:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1048, + "nodeType": "ExpressionStatement", + "src": "5092:81:3" + }, + { + "assignments": [ + 1050 + ], + "declarations": [ + { + "constant": false, + "id": 1050, + "mutability": "mutable", + "name": "bridgeMode", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1123, + "src": "5183:17:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 1049, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "5183:6:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1054, + "initialValue": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "expression": { + "argumentTypes": null, + "id": 1051, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 704, + "src": "5203:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 1052, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "getBridgeMode", + "nodeType": "MemberAccess", + "referencedDeclaration": 2967, + "src": "5203:28:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_pure$__$returns$_t_bytes4_$", + "typeString": "function () pure external returns (bytes4)" + } + }, + "id": 1053, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5203:30:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "5183:50:3" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "id": 1057, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1055, + "name": "bridgeMode", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1050, + "src": "5435:10:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30786231353136633236", + "id": 1056, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5449:10:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_2974903334_by_1", + "typeString": "int_const 2974903334" + }, + "value": "0xb1516c26" + }, + "src": "5435:24:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "id": 1073, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1071, + "name": "bridgeMode", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1050, + "src": "5581:10:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30783736353935623536", + "id": 1072, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5595:10:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1985567574_by_1", + "typeString": "int_const 1985567574" + }, + "value": "0x76595b56" + }, + "src": "5581:24:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": { + "id": 1087, + "nodeType": "Block", + "src": "5706:54:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "756e6b6e6f776e5f6272696467655f6d6f6465", + "id": 1084, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5727:21:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_445d91fc79f5d76a26706ce2f1c866b8b57fad4e5d19ffc80c8c136306007969", + "typeString": "literal_string \"unknown_bridge_mode\"" + }, + "value": "unknown_bridge_mode" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_445d91fc79f5d76a26706ce2f1c866b8b57fad4e5d19ffc80c8c136306007969", + "typeString": "literal_string \"unknown_bridge_mode\"" + } + ], + "id": 1083, + "name": "revert", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -19, + -19 + ], + "referencedDeclaration": -19, + "src": "5720:6:3", + "typeDescriptions": { + "typeIdentifier": "t_function_revert_pure$_t_string_memory_ptr_$returns$__$", + "typeString": "function (string memory) pure" + } + }, + "id": 1085, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5720:29:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1086, + "nodeType": "ExpressionStatement", + "src": "5720:29:3" + } + ] + }, + "id": 1088, + "nodeType": "IfStatement", + "src": "5578:182:3", + "trueBody": { + "id": 1082, + "nodeType": "Block", + "src": "5606:87:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1077, + "name": "sidechainAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 919, + "src": "5647:16:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_address_$", + "typeString": "function () view returns (address)" + } + }, + "id": 1078, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5647:18:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1079, + "name": "memberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1003, + "src": "5667:14:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1074, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 704, + "src": "5620:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 1076, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "relayTokens", + "nodeType": "MemberAccess", + "referencedDeclaration": 2962, + "src": "5620:26:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256) external" + } + }, + "id": 1080, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5620:62:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1081, + "nodeType": "ExpressionStatement", + "src": "5620:62:3" + } + ] + } + }, + "id": 1089, + "nodeType": "IfStatement", + "src": "5432:328:3", + "trueBody": { + "id": 1070, + "nodeType": "Block", + "src": "5461:103:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1063, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 710, + "src": "5510:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + ], + "id": 1062, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "5502:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1061, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "5502:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1064, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5502:14:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1065, + "name": "sidechainAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 919, + "src": "5518:16:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_address_$", + "typeString": "function () view returns (address)" + } + }, + "id": 1066, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5518:18:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1067, + "name": "memberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1003, + "src": "5538:14:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1058, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 704, + "src": "5475:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 1060, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "relayTokens", + "nodeType": "MemberAccess", + "referencedDeclaration": 2955, + "src": "5475:26:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,address,uint256) external" + } + }, + "id": 1068, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5475:78:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1069, + "nodeType": "ExpressionStatement", + "src": "5475:78:3" + } + ] + } + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1094, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1091, + "name": "unaccountedTokens", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 970, + "src": "5824:17:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", + "typeString": "function () view returns (uint256)" + } + }, + "id": 1092, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5824:19:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1093, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5847:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "5824:24:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6e6f745f7472616e73666572726564", + "id": 1095, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5850:17:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_f8c2113376f862fd95528ef214ec6c4df8073d6ec53e0649e3a444e38c7f206c", + "typeString": "literal_string \"not_transferred\"" + }, + "value": "not_transferred" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_f8c2113376f862fd95528ef214ec6c4df8073d6ec53e0649e3a444e38c7f206c", + "typeString": "literal_string \"not_transferred\"" + } + ], + "id": 1090, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "5816:7:3", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1096, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5816:52:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1097, + "nodeType": "ExpressionStatement", + "src": "5816:52:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 1103, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1098, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 730, + "src": "5878:13:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1101, + "name": "memberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1003, + "src": "5912:14:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1099, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 730, + "src": "5894:13:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1100, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "5894:17:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1102, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5894:33:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "5878:49:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1104, + "nodeType": "ExpressionStatement", + "src": "5878:49:3" + }, + { + "assignments": [ + 1106 + ], + "declarations": [ + { + "constant": false, + "id": 1106, + "mutability": "mutable", + "name": "data", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1123, + "src": "5938:17:3", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 1105, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "5938:5:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1111, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "72656672657368526576656e75652829", + "id": 1109, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5982:18:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_331beb5f2892eb3ad8da75941af8f45f6a0e8580b91ecbb4c7fdf0219d1442a8", + "typeString": "literal_string \"refreshRevenue()\"" + }, + "value": "refreshRevenue()" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_331beb5f2892eb3ad8da75941af8f45f6a0e8580b91ecbb4c7fdf0219d1442a8", + "typeString": "literal_string \"refreshRevenue()\"" + } + ], + "expression": { + "argumentTypes": null, + "id": 1107, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "5958:3:3", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 1108, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encodeWithSignature", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "5958:23:3", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencodewithsignature_pure$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (string memory) pure returns (bytes memory)" + } + }, + "id": 1110, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5958:43:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "5938:63:3" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1115, + "name": "sidechainAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 919, + "src": "6036:16:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_address_$", + "typeString": "function () view returns (address)" + } + }, + "id": 1116, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6036:18:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1117, + "name": "data", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1106, + "src": "6056:4:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "id": 1118, + "name": "sidechain_maxgas", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 708, + "src": "6062:16:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1112, + "name": "amb", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 702, + "src": "6011:3:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "id": 1114, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "requireToPassMessage", + "nodeType": "MemberAccess", + "referencedDeclaration": 2911, + "src": "6011:24:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_bytes_memory_ptr_$_t_uint256_$returns$_t_bytes32_$", + "typeString": "function (address,bytes memory,uint256) external returns (bytes32)" + } + }, + "id": 1119, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6011:68:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "id": 1120, + "nodeType": "ExpressionStatement", + "src": "6011:68:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 1121, + "name": "newTokens", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 976, + "src": "6096:9:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 974, + "id": 1122, + "nodeType": "Return", + "src": "6089:16:3" + } + ] + }, + "documentation": null, + "functionSelector": "2efc1007", + "id": 1124, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "sendTokensToBridge", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 971, + "nodeType": "ParameterList", + "parameters": [], + "src": "4534:2:3" + }, + "returnParameters": { + "id": 974, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 973, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1124, + "src": "4553:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 972, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4553:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4552:9:3" + }, + "scope": 1165, + "src": "4507:1605:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1163, + "nodeType": "Block", + "src": "6172:337:3", + "statements": [ + { + "assignments": [ + 1130 + ], + "declarations": [ + { + "constant": false, + "id": 1130, + "mutability": "mutable", + "name": "withdrawable", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1163, + "src": "6182:20:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1129, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "6182:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1133, + "initialValue": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1131, + "name": "adminFeesWithdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 952, + "src": "6205:21:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", + "typeString": "function () view returns (uint256)" + } + }, + "id": 1132, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6205:23:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "6182:46:3" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1136, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1134, + "name": "withdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1130, + "src": "6242:12:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1135, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "6258:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "6242:17:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 1139, + "nodeType": "IfStatement", + "src": "6238:31:3", + "trueBody": { + "expression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1137, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "6268:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "functionReturnParameters": 1128, + "id": 1138, + "nodeType": "Return", + "src": "6261:8:3" + } + }, + { + "expression": { + "argumentTypes": null, + "id": 1145, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1140, + "name": "totalAdminFeesWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 718, + "src": "6279:23:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1143, + "name": "withdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1130, + "src": "6333:12:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1141, + "name": "totalAdminFeesWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 718, + "src": "6305:23:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1142, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "6305:27:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1144, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6305:41:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "6279:67:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1146, + "nodeType": "ExpressionStatement", + "src": "6279:67:3" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1150, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "6379:5:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1151, + "name": "withdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1130, + "src": "6386:12:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1148, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 710, + "src": "6364:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "id": 1149, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "transfer", + "nodeType": "MemberAccess", + "referencedDeclaration": 3769, + "src": "6364:14:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", + "typeString": "function (address,uint256) external returns (bool)" + } + }, + "id": 1152, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6364:35:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "7472616e736665725f6661696c6564", + "id": 1153, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "6401:17:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_b8e9fee0fffb4680cf1dadd39ef447f97bcb04e1ca58d043770ea51a3c935e6c", + "typeString": "literal_string \"transfer_failed\"" + }, + "value": "transfer_failed" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_b8e9fee0fffb4680cf1dadd39ef447f97bcb04e1ca58d043770ea51a3c935e6c", + "typeString": "literal_string \"transfer_failed\"" + } + ], + "id": 1147, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "6356:7:3", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1154, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6356:63:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1155, + "nodeType": "ExpressionStatement", + "src": "6356:63:3" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1157, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "6453:5:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1158, + "name": "withdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1130, + "src": "6460:12:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1156, + "name": "AdminFeesWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 696, + "src": "6434:18:3", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 1159, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6434:39:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1160, + "nodeType": "EmitStatement", + "src": "6429:44:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 1161, + "name": "withdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1130, + "src": "6490:12:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 1128, + "id": 1162, + "nodeType": "Return", + "src": "6483:19:3" + } + ] + }, + "documentation": null, + "functionSelector": "0419b45a", + "id": 1164, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdrawAdminFees", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1125, + "nodeType": "ParameterList", + "parameters": [], + "src": "6144:2:3" + }, + "returnParameters": { + "id": 1128, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1127, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1164, + "src": "6163:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1126, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "6163:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "6162:9:3" + }, + "scope": 1165, + "src": "6118:391:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + } + ], + "scope": 1166, + "src": "356:6155:3" + } + ], + "src": "0:6512:3" + }, + "legacyAST": { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/DataUnionMainnet.sol", + "exportedSymbols": { + "DataUnionMainnet": [ + 1165 + ] + }, + "id": 1166, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 668, + "literals": [ + "solidity", + "0.6", + ".6" + ], + "nodeType": "PragmaDirective", + "src": "0:22:3" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol", + "file": "openzeppelin-solidity/contracts/token/ERC20/ERC20.sol", + "id": 669, + "nodeType": "ImportDirective", + "scope": 1166, + "sourceUnit": 4149, + "src": "24:63:3", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "file": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "id": 670, + "nodeType": "ImportDirective", + "scope": 1166, + "sourceUnit": 3642, + "src": "88:59:3", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/Ownable.sol", + "file": "./Ownable.sol", + "id": 671, + "nodeType": "ImportDirective", + "scope": 1166, + "sourceUnit": 3181, + "src": "148:23:3", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/PurchaseListener.sol", + "file": "./PurchaseListener.sol", + "id": 672, + "nodeType": "ImportDirective", + "scope": 1166, + "sourceUnit": 3200, + "src": "245:32:3", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/CloneLib.sol", + "file": "./CloneLib.sol", + "id": 673, + "nodeType": "ImportDirective", + "scope": 1166, + "sourceUnit": 155, + "src": "278:24:3", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/IAMB.sol", + "file": "./IAMB.sol", + "id": 674, + "nodeType": "ImportDirective", + "scope": 1166, + "sourceUnit": 2913, + "src": "303:20:3", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/ITokenMediator.sol", + "file": "./ITokenMediator.sol", + "id": 675, + "nodeType": "ImportDirective", + "scope": 1166, + "sourceUnit": 2969, + "src": "324:30:3", + "symbolAliases": [], + "unitAlias": "" + }, + { + "abstract": false, + "baseContracts": [ + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 676, + "name": "Ownable", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 3180, + "src": "385:7:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_Ownable_$3180", + "typeString": "contract Ownable" + } + }, + "id": 677, + "nodeType": "InheritanceSpecifier", + "src": "385:7:3" + }, + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 678, + "name": "PurchaseListener", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 3199, + "src": "394:16:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_PurchaseListener_$3199", + "typeString": "contract PurchaseListener" + } + }, + "id": 679, + "nodeType": "InheritanceSpecifier", + "src": "394:16:3" + } + ], + "contractDependencies": [ + 3180, + 3199 + ], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 1165, + "linearizedBaseContracts": [ + 1165, + 3199, + 3180 + ], + "name": "DataUnionMainnet", + "nodeType": "ContractDefinition", + "nodes": [ + { + "id": 682, + "libraryName": { + "contractScope": null, + "id": 680, + "name": "SafeMath", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 3641, + "src": "423:8:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_SafeMath_$3641", + "typeString": "library SafeMath" + } + }, + "nodeType": "UsingForDirective", + "src": "417:27:3", + "typeName": { + "id": 681, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "436:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + }, + { + "anonymous": false, + "documentation": null, + "id": 686, + "name": "AdminFeeChanged", + "nodeType": "EventDefinition", + "parameters": { + "id": 685, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 684, + "indexed": false, + "mutability": "mutable", + "name": "adminFee", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 686, + "src": "472:16:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 683, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "472:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "471:18:3" + }, + "src": "450:40:3" + }, + { + "anonymous": false, + "documentation": null, + "id": 690, + "name": "AdminFeeCharged", + "nodeType": "EventDefinition", + "parameters": { + "id": 689, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 688, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 690, + "src": "517:14:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 687, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "517:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "516:16:3" + }, + "src": "495:38:3" + }, + { + "anonymous": false, + "documentation": null, + "id": 696, + "name": "AdminFeesWithdrawn", + "nodeType": "EventDefinition", + "parameters": { + "id": 695, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 692, + "indexed": true, + "mutability": "mutable", + "name": "admin", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 696, + "src": "563:21:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 691, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "563:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 694, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 696, + "src": "586:14:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 693, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "586:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "562:39:3" + }, + "src": "538:64:3" + }, + { + "anonymous": false, + "documentation": null, + "id": 700, + "name": "RevenueReceived", + "nodeType": "EventDefinition", + "parameters": { + "id": 699, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 698, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 700, + "src": "630:14:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 697, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "630:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "629:16:3" + }, + "src": "608:38:3" + }, + { + "constant": false, + "functionSelector": "1062b39a", + "id": 702, + "mutability": "mutable", + "name": "amb", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "652:15:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + }, + "typeName": { + "contractScope": null, + "id": 701, + "name": "IAMB", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 2912, + "src": "652:4:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "c5a8c91f", + "id": 704, + "mutability": "mutable", + "name": "token_mediator", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "673:36:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + }, + "typeName": { + "contractScope": null, + "id": 703, + "name": "ITokenMediator", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 2968, + "src": "673:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "e22ec112", + "id": 706, + "mutability": "mutable", + "name": "sidechain_DU_factory", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "715:35:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 705, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "715:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "692199d4", + "id": 708, + "mutability": "mutable", + "name": "sidechain_maxgas", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "756:31:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 707, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "756:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "fc0c546a", + "id": 710, + "mutability": "mutable", + "name": "token", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "793:18:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + }, + "typeName": { + "contractScope": null, + "id": 709, + "name": "ERC20", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 4148, + "src": "793:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "a65c87d9", + "id": 712, + "mutability": "mutable", + "name": "sidechain_template_DU", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "978:36:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 711, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "978:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "d35cec40", + "id": 714, + "mutability": "mutable", + "name": "adminFeeFraction", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "1020:31:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 713, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1020:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "61feacff", + "id": 716, + "mutability": "mutable", + "name": "totalAdminFees", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "1057:29:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 715, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1057:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "a913f41d", + "id": 718, + "mutability": "mutable", + "name": "totalAdminFeesWithdrawn", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "1092:38:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 717, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1092:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "9eeba07c", + "id": 720, + "mutability": "mutable", + "name": "autoSendAdminFee", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "1136:28:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 719, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "1136:4:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "public" + }, + { + "body": { + "id": 727, + "nodeType": "Block", + "src": "1220:13:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "hexValue": "32", + "id": 725, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1229:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_2_by_1", + "typeString": "int_const 2" + }, + "value": "2" + }, + "functionReturnParameters": 724, + "id": 726, + "nodeType": "Return", + "src": "1222:8:3" + } + ] + }, + "documentation": null, + "functionSelector": "54fd4d50", + "id": 728, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "version", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 721, + "nodeType": "ParameterList", + "parameters": [], + "src": "1187:2:3" + }, + "returnParameters": { + "id": 724, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 723, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 728, + "src": "1211:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 722, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1211:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1210:9:3" + }, + "scope": 1165, + "src": "1171:62:3", + "stateMutability": "pure", + "virtual": false, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "2df3eba4", + "id": 730, + "mutability": "mutable", + "name": "totalEarnings", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1165, + "src": "1443:28:3", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 729, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1443:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "body": { + "id": 739, + "nodeType": "Block", + "src": "1520:2:3", + "statements": [] + }, + "documentation": null, + "id": 740, + "implemented": true, + "kind": "constructor", + "modifiers": [ + { + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "30", + "id": 735, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1516:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "id": 734, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "1508:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 733, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1508:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 736, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1508:10:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "id": 737, + "modifierName": { + "argumentTypes": null, + "id": 732, + "name": "Ownable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3180, + "src": "1500:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_Ownable_$3180_$", + "typeString": "type(contract Ownable)" + } + }, + "nodeType": "ModifierInvocation", + "src": "1500:19:3" + } + ], + "name": "", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 731, + "nodeType": "ParameterList", + "parameters": [], + "src": "1490:2:3" + }, + "returnParameters": { + "id": 738, + "nodeType": "ParameterList", + "parameters": [], + "src": "1520:0:3" + }, + "scope": 1165, + "src": "1479:43:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 820, + "nodeType": "Block", + "src": "1825:668:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 763, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "!", + "prefix": true, + "src": "1843:16:3", + "subExpression": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 761, + "name": "isInitialized", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 837, + "src": "1844:13:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_bool_$", + "typeString": "function () view returns (bool)" + } + }, + "id": 762, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1844:15:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "696e69745f6f6e6365", + "id": 764, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1861:11:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_489583184b3bb72a2d8c2cf7b5f4c46635517588498db21bc763656f9e5ab21e", + "typeString": "literal_string \"init_once\"" + }, + "value": "init_once" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_489583184b3bb72a2d8c2cf7b5f4c46635517588498db21bc763656f9e5ab21e", + "typeString": "literal_string \"init_once\"" + } + ], + "id": 760, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "1835:7:3", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 765, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1835:38:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 766, + "nodeType": "ExpressionStatement", + "src": "1835:38:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 769, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 767, + "name": "autoSendAdminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 720, + "src": "1956:16:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "hexValue": "74727565", + "id": 768, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "1975:4:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "src": "1956:23:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 770, + "nodeType": "ExpressionStatement", + "src": "1956:23:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 774, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 771, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "2034:5:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 772, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "2042:3:3", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 773, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "2042:10:3", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "src": "2034:18:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 775, + "nodeType": "ExpressionStatement", + "src": "2034:18:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 780, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 776, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 704, + "src": "2063:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 778, + "name": "_token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 744, + "src": "2095:15:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 777, + "name": "ITokenMediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2968, + "src": "2080:14:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_ITokenMediator_$2968_$", + "typeString": "type(contract ITokenMediator)" + } + }, + "id": 779, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2080:31:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "src": "2063:48:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 781, + "nodeType": "ExpressionStatement", + "src": "2063:48:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 788, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 782, + "name": "amb", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 702, + "src": "2121:3:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "expression": { + "argumentTypes": null, + "id": 784, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 704, + "src": "2132:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 785, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "bridgeContract", + "nodeType": "MemberAccess", + "referencedDeclaration": 2946, + "src": "2132:29:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$__$returns$_t_address_$", + "typeString": "function () view external returns (address)" + } + }, + "id": 786, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2132:31:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 783, + "name": "IAMB", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2912, + "src": "2127:4:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_IAMB_$2912_$", + "typeString": "type(contract IAMB)" + } + }, + "id": 787, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2127:37:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "src": "2121:43:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "id": 789, + "nodeType": "ExpressionStatement", + "src": "2121:43:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 794, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 790, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 710, + "src": "2174:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 792, + "name": "_token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 742, + "src": "2188:6:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 791, + "name": "ERC20", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 4148, + "src": "2182:5:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_ERC20_$4148_$", + "typeString": "type(contract ERC20)" + } + }, + "id": 793, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2182:13:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "src": "2174:21:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "id": 795, + "nodeType": "ExpressionStatement", + "src": "2174:21:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 798, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 796, + "name": "sidechain_DU_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 706, + "src": "2205:20:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 797, + "name": "_sidechain_DU_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 746, + "src": "2228:21:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "2205:44:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 799, + "nodeType": "ExpressionStatement", + "src": "2205:44:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 802, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 800, + "name": "sidechain_maxgas", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 708, + "src": "2259:16:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 801, + "name": "_sidechain_maxgas", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 748, + "src": "2278:17:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "2259:36:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 803, + "nodeType": "ExpressionStatement", + "src": "2259:36:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 806, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 804, + "name": "sidechain_template_DU", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 712, + "src": "2305:21:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 805, + "name": "_sidechain_template_DU", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 750, + "src": "2329:22:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "2305:46:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 807, + "nodeType": "ExpressionStatement", + "src": "2305:46:3" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 809, + "name": "_adminFeeFraction", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 754, + "src": "2373:17:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 808, + "name": "setAdminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 861, + "src": "2361:11:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 810, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2361:30:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 811, + "nodeType": "ExpressionStatement", + "src": "2361:30:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 814, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 812, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "2434:5:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 813, + "name": "_owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 752, + "src": "2442:6:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "2434:14:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 815, + "nodeType": "ExpressionStatement", + "src": "2434:14:3" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 817, + "name": "agents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 757, + "src": "2479:6:3", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + ], + "id": 816, + "name": "deployNewDUSidechain", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 897, + "src": "2458:20:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_array$_t_address_$dyn_memory_ptr_$returns$__$", + "typeString": "function (address[] memory)" + } + }, + "id": 818, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2458:28:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 819, + "nodeType": "ExpressionStatement", + "src": "2458:28:3" + } + ] + }, + "documentation": null, + "functionSelector": "fb6470c9", + "id": 821, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "initialize", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 758, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 742, + "mutability": "mutable", + "name": "_token", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1557:14:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 741, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1557:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 744, + "mutability": "mutable", + "name": "_token_mediator", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1581:23:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 743, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1581:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 746, + "mutability": "mutable", + "name": "_sidechain_DU_factory", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1614:29:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 745, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1614:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 748, + "mutability": "mutable", + "name": "_sidechain_maxgas", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1653:25:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 747, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1653:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 750, + "mutability": "mutable", + "name": "_sidechain_template_DU", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1688:30:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 749, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1688:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 752, + "mutability": "mutable", + "name": "_owner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1728:14:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 751, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1728:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 754, + "mutability": "mutable", + "name": "_adminFeeFraction", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1752:25:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 753, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1752:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 757, + "mutability": "mutable", + "name": "agents", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 821, + "src": "1787:23:3", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 755, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1787:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 756, + "length": null, + "nodeType": "ArrayTypeName", + "src": "1787:9:3", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1547:269:3" + }, + "returnParameters": { + "id": 759, + "nodeType": "ParameterList", + "parameters": [], + "src": "1825:0:3" + }, + "scope": 1165, + "src": "1528:965:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 836, + "nodeType": "Block", + "src": "2551:52:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 834, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 828, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 710, + "src": "2576:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + ], + "id": 827, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2568:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 826, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2568:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 829, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2568:14:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "30", + "id": 832, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2594:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "id": 831, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2586:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 830, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2586:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 833, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2586:10:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "src": "2568:28:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "functionReturnParameters": 825, + "id": 835, + "nodeType": "Return", + "src": "2561:35:3" + } + ] + }, + "documentation": null, + "functionSelector": "392e53cd", + "id": 837, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "isInitialized", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 822, + "nodeType": "ParameterList", + "parameters": [], + "src": "2521:2:3" + }, + "returnParameters": { + "id": 825, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 824, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 837, + "src": "2545:4:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 823, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "2545:4:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2544:6:3" + }, + "scope": 1165, + "src": "2499:104:3", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 860, + "nodeType": "Block", + "src": "2842:154:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 848, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 846, + "name": "newAdminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 840, + "src": "2860:11:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "<=", + "rightExpression": { + "argumentTypes": null, + "hexValue": "31", + "id": 847, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2875:7:3", + "subdenomination": "ether", + "typeDescriptions": { + "typeIdentifier": "t_rational_1000000000000000000_by_1", + "typeString": "int_const 1000000000000000000" + }, + "value": "1" + }, + "src": "2860:22:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f61646d696e466565", + "id": 849, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2884:16:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_c137fd2ee13f1c30c5cc71b65e2c7f49f5120b580d70d8298b6185b09e6c1e40", + "typeString": "literal_string \"error_adminFee\"" + }, + "value": "error_adminFee" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_c137fd2ee13f1c30c5cc71b65e2c7f49f5120b580d70d8298b6185b09e6c1e40", + "typeString": "literal_string \"error_adminFee\"" + } + ], + "id": 845, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "2852:7:3", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 850, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2852:49:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 851, + "nodeType": "ExpressionStatement", + "src": "2852:49:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 854, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 852, + "name": "adminFeeFraction", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 714, + "src": "2911:16:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 853, + "name": "newAdminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 840, + "src": "2930:11:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "2911:30:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 855, + "nodeType": "ExpressionStatement", + "src": "2911:30:3" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 857, + "name": "adminFeeFraction", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 714, + "src": "2972:16:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 856, + "name": "AdminFeeChanged", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 686, + "src": "2956:15:3", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 858, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2956:33:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 859, + "nodeType": "EmitStatement", + "src": "2951:38:3" + } + ] + }, + "documentation": { + "id": 838, + "nodeType": "StructuredDocumentation", + "src": "2609:169:3", + "text": "Admin fee as a fraction of revenue.\n@param newAdminFee fixed-point decimal in the same way as ether: 50% === 0.5 ether === \"500000000000000000\"" + }, + "functionSelector": "8beb60b6", + "id": 861, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 843, + "modifierName": { + "argumentTypes": null, + "id": 842, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "2832:9:3", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "2832:9:3" + } + ], + "name": "setAdminFee", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 841, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 840, + "mutability": "mutable", + "name": "newAdminFee", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 861, + "src": "2804:19:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 839, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "2804:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2803:21:3" + }, + "returnParameters": { + "id": 844, + "nodeType": "ParameterList", + "parameters": [], + "src": "2842:0:3" + }, + "scope": 1165, + "src": "2783:213:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 872, + "nodeType": "Block", + "src": "3063:44:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 870, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 868, + "name": "autoSendAdminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 720, + "src": "3073:16:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 869, + "name": "autoSend", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 863, + "src": "3092:8:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "3073:27:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 871, + "nodeType": "ExpressionStatement", + "src": "3073:27:3" + } + ] + }, + "documentation": null, + "functionSelector": "0f3afcbe", + "id": 873, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 866, + "modifierName": { + "argumentTypes": null, + "id": 865, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "3053:9:3", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "3053:9:3" + } + ], + "name": "setAutoSendAdminFee", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 864, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 863, + "mutability": "mutable", + "name": "autoSend", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 873, + "src": "3031:13:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 862, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "3031:4:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "3030:15:3" + }, + "returnParameters": { + "id": 867, + "nodeType": "ParameterList", + "parameters": [], + "src": "3063:0:3" + }, + "scope": 1165, + "src": "3002:105:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 896, + "nodeType": "Block", + "src": "3176:198:3", + "statements": [ + { + "assignments": [ + 880 + ], + "declarations": [ + { + "constant": false, + "id": 880, + "mutability": "mutable", + "name": "data", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 896, + "src": "3186:17:3", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 879, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "3186:5:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 887, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "6465706c6f794e6577445553696465636861696e28616464726573732c616464726573735b5d29", + "id": 883, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3230:41:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_325ff66f80d01e2243bd492c3b38e663416cd478dca0ff43243ddb11328dca3f", + "typeString": "literal_string \"deployNewDUSidechain(address,address[])\"" + }, + "value": "deployNewDUSidechain(address,address[])" + }, + { + "argumentTypes": null, + "id": 884, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "3273:5:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 885, + "name": "agents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 876, + "src": "3280:6:3", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_325ff66f80d01e2243bd492c3b38e663416cd478dca0ff43243ddb11328dca3f", + "typeString": "literal_string \"deployNewDUSidechain(address,address[])\"" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + ], + "expression": { + "argumentTypes": null, + "id": 881, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "3206:3:3", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 882, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encodeWithSignature", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "3206:23:3", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencodewithsignature_pure$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (string memory) pure returns (bytes memory)" + } + }, + "id": 886, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3206:81:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "3186:101:3" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 891, + "name": "sidechain_DU_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 706, + "src": "3322:20:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 892, + "name": "data", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 880, + "src": "3344:4:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "id": 893, + "name": "sidechain_maxgas", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 708, + "src": "3350:16:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 888, + "name": "amb", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 702, + "src": "3297:3:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "id": 890, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "requireToPassMessage", + "nodeType": "MemberAccess", + "referencedDeclaration": 2911, + "src": "3297:24:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_bytes_memory_ptr_$_t_uint256_$returns$_t_bytes32_$", + "typeString": "function (address,bytes memory,uint256) external returns (bytes32)" + } + }, + "id": 894, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3297:70:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "id": 895, + "nodeType": "ExpressionStatement", + "src": "3297:70:3" + } + ] + }, + "documentation": null, + "functionSelector": "99dd1c81", + "id": 897, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "deployNewDUSidechain", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 877, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 876, + "mutability": "mutable", + "name": "agents", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 897, + "src": "3144:23:3", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 874, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3144:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 875, + "length": null, + "nodeType": "ArrayTypeName", + "src": "3144:9:3", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "3143:25:3" + }, + "returnParameters": { + "id": 878, + "nodeType": "ParameterList", + "parameters": [], + "src": "3176:0:3" + }, + "scope": 1165, + "src": "3114:260:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 918, + "nodeType": "Block", + "src": "3438:137:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 904, + "name": "sidechain_template_DU", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 712, + "src": "3491:21:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 905, + "name": "sidechain_DU_factory", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 706, + "src": "3514:20:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 912, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "3560:4:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionMainnet_$1165", + "typeString": "contract DataUnionMainnet" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionMainnet_$1165", + "typeString": "contract DataUnionMainnet" + } + ], + "id": 911, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "3552:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 910, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3552:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 913, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3552:13:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 909, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "3544:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_uint256_$", + "typeString": "type(uint256)" + }, + "typeName": { + "id": 908, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "3544:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 914, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3544:22:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 907, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "3536:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_bytes32_$", + "typeString": "type(bytes32)" + }, + "typeName": { + "id": 906, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "3536:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 915, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3536:31:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "expression": { + "argumentTypes": null, + "id": 902, + "name": "CloneLib", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 154, + "src": "3455:8:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_CloneLib_$154_$", + "typeString": "type(library CloneLib)" + } + }, + "id": 903, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "predictCloneAddressCreate2", + "nodeType": "MemberAccess", + "referencedDeclaration": 61, + "src": "3455:35:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_address_$_t_address_$_t_bytes32_$returns$_t_address_$", + "typeString": "function (address,address,bytes32) pure returns (address)" + } + }, + "id": 916, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3455:113:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "functionReturnParameters": 901, + "id": 917, + "nodeType": "Return", + "src": "3448:120:3" + } + ] + }, + "documentation": null, + "functionSelector": "37b43a94", + "id": 919, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "sidechainAddress", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 898, + "nodeType": "ParameterList", + "parameters": [], + "src": "3405:2:3" + }, + "returnParameters": { + "id": 901, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 900, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 919, + "src": "3429:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 899, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3429:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "3428:9:3" + }, + "scope": 1165, + "src": "3380:195:3", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "baseFunctions": [ + 3198 + ], + "body": { + "id": 940, + "nodeType": "Block", + "src": "4158:58:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 935, + "name": "sendTokensToBridge", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1124, + "src": "4168:18:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$__$returns$_t_uint256_$", + "typeString": "function () returns (uint256)" + } + }, + "id": 936, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4168:20:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 937, + "nodeType": "ExpressionStatement", + "src": "4168:20:3" + }, + { + "expression": { + "argumentTypes": null, + "hexValue": "74727565", + "id": 938, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "bool", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4205:4:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "value": "true" + }, + "functionReturnParameters": 934, + "id": 939, + "nodeType": "Return", + "src": "4198:11:3" + } + ] + }, + "documentation": null, + "functionSelector": "4a439cc0", + "id": 941, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "onPurchase", + "nodeType": "FunctionDefinition", + "overrides": { + "id": 931, + "nodeType": "OverrideSpecifier", + "overrides": [], + "src": "4134:8:3" + }, + "parameters": { + "id": 930, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 921, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 941, + "src": "4080:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 920, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "4080:7:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 923, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 941, + "src": "4089:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 922, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4089:7:3", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 925, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 941, + "src": "4098:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 924, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4098:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 927, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 941, + "src": "4107:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 926, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4107:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 929, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 941, + "src": "4116:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 928, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4116:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4079:45:3" + }, + "returnParameters": { + "id": 934, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 933, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 941, + "src": "4152:4:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 932, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "4152:4:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4151:6:3" + }, + "scope": 1165, + "src": "4060:156:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "external" + }, + { + "body": { + "id": 951, + "nodeType": "Block", + "src": "4285:67:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 948, + "name": "totalAdminFeesWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 718, + "src": "4321:23:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 946, + "name": "totalAdminFees", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 716, + "src": "4302:14:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 947, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "4302:18:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 949, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4302:43:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 945, + "id": 950, + "nodeType": "Return", + "src": "4295:50:3" + } + ] + }, + "documentation": null, + "functionSelector": "13fd3c56", + "id": 952, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "adminFeesWithdrawable", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 942, + "nodeType": "ParameterList", + "parameters": [], + "src": "4252:2:3" + }, + "returnParameters": { + "id": 945, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 944, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 952, + "src": "4276:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 943, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4276:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4275:9:3" + }, + "scope": 1165, + "src": "4222:130:3", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 969, + "nodeType": "Block", + "src": "4417:83:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 965, + "name": "adminFeesWithdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 952, + "src": "4469:21:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", + "typeString": "function () view returns (uint256)" + } + }, + "id": 966, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4469:23:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 961, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "4458:4:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionMainnet_$1165", + "typeString": "contract DataUnionMainnet" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionMainnet_$1165", + "typeString": "contract DataUnionMainnet" + } + ], + "id": 960, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "4450:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 959, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4450:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 962, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4450:13:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "expression": { + "argumentTypes": null, + "id": 957, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 710, + "src": "4434:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "id": 958, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "balanceOf", + "nodeType": "MemberAccess", + "referencedDeclaration": 3748, + "src": "4434:15:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view external returns (uint256)" + } + }, + "id": 963, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4434:30:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 964, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "4434:34:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 967, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4434:59:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 956, + "id": 968, + "nodeType": "Return", + "src": "4427:66:3" + } + ] + }, + "documentation": null, + "functionSelector": "132b4194", + "id": 970, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "unaccountedTokens", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 953, + "nodeType": "ParameterList", + "parameters": [], + "src": "4384:2:3" + }, + "returnParameters": { + "id": 956, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 955, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 970, + "src": "4408:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 954, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4408:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4407:9:3" + }, + "scope": 1165, + "src": "4358:142:3", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1123, + "nodeType": "Block", + "src": "4562:1550:3", + "statements": [ + { + "assignments": [ + 976 + ], + "declarations": [ + { + "constant": false, + "id": 976, + "mutability": "mutable", + "name": "newTokens", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1123, + "src": "4572:17:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 975, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4572:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 979, + "initialValue": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 977, + "name": "unaccountedTokens", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 970, + "src": "4592:17:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", + "typeString": "function () view returns (uint256)" + } + }, + "id": 978, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4592:19:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "4572:39:3" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 982, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 980, + "name": "newTokens", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 976, + "src": "4625:9:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 981, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4638:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "4625:14:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 985, + "nodeType": "IfStatement", + "src": "4621:28:3", + "trueBody": { + "expression": { + "argumentTypes": null, + "hexValue": "30", + "id": 983, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4648:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "functionReturnParameters": 974, + "id": 984, + "nodeType": "Return", + "src": "4641:8:3" + } + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 987, + "name": "newTokens", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 976, + "src": "4681:9:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 986, + "name": "RevenueReceived", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 700, + "src": "4665:15:3", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 988, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4665:26:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 989, + "nodeType": "EmitStatement", + "src": "4660:31:3" + }, + { + "assignments": [ + 991 + ], + "declarations": [ + { + "constant": false, + "id": 991, + "mutability": "mutable", + "name": "adminFee", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1123, + "src": "4702:16:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 990, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4702:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1001, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_rational_1000000000000000000_by_1", + "typeString": "int_const 1000000000000000000" + }, + "id": 999, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "hexValue": "3130", + "id": 997, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4757:2:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_10_by_1", + "typeString": "int_const 10" + }, + "value": "10" + }, + "nodeType": "BinaryOperation", + "operator": "**", + "rightExpression": { + "argumentTypes": null, + "hexValue": "3138", + "id": 998, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4761:2:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_18_by_1", + "typeString": "int_const 18" + }, + "value": "18" + }, + "src": "4757:6:3", + "typeDescriptions": { + "typeIdentifier": "t_rational_1000000000000000000_by_1", + "typeString": "int_const 1000000000000000000" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1000000000000000000_by_1", + "typeString": "int_const 1000000000000000000" + } + ], + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 994, + "name": "adminFeeFraction", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 714, + "src": "4735:16:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 992, + "name": "newTokens", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 976, + "src": "4721:9:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 993, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "mul", + "nodeType": "MemberAccess", + "referencedDeclaration": 3554, + "src": "4721:13:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 995, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4721:31:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 996, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "div", + "nodeType": "MemberAccess", + "referencedDeclaration": 3571, + "src": "4721:35:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1000, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4721:43:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "4702:62:3" + }, + { + "assignments": [ + 1003 + ], + "declarations": [ + { + "constant": false, + "id": 1003, + "mutability": "mutable", + "name": "memberEarnings", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1123, + "src": "4774:22:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1002, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4774:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1008, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1006, + "name": "adminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 991, + "src": "4813:8:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1004, + "name": "newTokens", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 976, + "src": "4799:9:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1005, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "4799:13:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1007, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4799:23:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "4774:48:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 1014, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1009, + "name": "totalAdminFees", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 716, + "src": "4833:14:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1012, + "name": "adminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 991, + "src": "4869:8:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1010, + "name": "totalAdminFees", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 716, + "src": "4850:14:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1011, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "4850:18:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1013, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4850:28:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "4833:45:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1015, + "nodeType": "ExpressionStatement", + "src": "4833:45:3" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1017, + "name": "adminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 991, + "src": "4909:8:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1016, + "name": "AdminFeeCharged", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 690, + "src": "4893:15:3", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 1018, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4893:25:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1019, + "nodeType": "EmitStatement", + "src": "4888:30:3" + }, + { + "condition": { + "argumentTypes": null, + "id": 1020, + "name": "autoSendAdminFee", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 720, + "src": "4931:16:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 1024, + "nodeType": "IfStatement", + "src": "4928:40:3", + "trueBody": { + "expression": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1021, + "name": "withdrawAdminFees", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1164, + "src": "4949:17:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$__$returns$_t_uint256_$", + "typeString": "function () returns (uint256)" + } + }, + "id": 1022, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4949:19:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1023, + "nodeType": "ExpressionStatement", + "src": "4949:19:3" + } + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1030, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 704, + "src": "5044:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + ], + "id": 1029, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "5036:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1028, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "5036:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1031, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5036:23:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "hexValue": "30", + "id": 1032, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5061:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "expression": { + "argumentTypes": null, + "id": 1026, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 710, + "src": "5022:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "id": 1027, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "approve", + "nodeType": "MemberAccess", + "referencedDeclaration": 3808, + "src": "5022:13:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", + "typeString": "function (address,uint256) external returns (bool)" + } + }, + "id": 1033, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5022:41:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "617070726f76655f6661696c6564", + "id": 1034, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5065:16:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_0b99e75f7873826fa8b33e45f5babad412309f79417b9b0000257bd68fa2b8a3", + "typeString": "literal_string \"approve_failed\"" + }, + "value": "approve_failed" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_0b99e75f7873826fa8b33e45f5babad412309f79417b9b0000257bd68fa2b8a3", + "typeString": "literal_string \"approve_failed\"" + } + ], + "id": 1025, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "5014:7:3", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1035, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5014:68:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1036, + "nodeType": "ExpressionStatement", + "src": "5014:68:3" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1042, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 704, + "src": "5122:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + ], + "id": 1041, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "5114:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1040, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "5114:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1043, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5114:23:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1044, + "name": "memberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1003, + "src": "5139:14:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1038, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 710, + "src": "5100:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "id": 1039, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "approve", + "nodeType": "MemberAccess", + "referencedDeclaration": 3808, + "src": "5100:13:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", + "typeString": "function (address,uint256) external returns (bool)" + } + }, + "id": 1045, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5100:54:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "617070726f76655f6661696c6564", + "id": 1046, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5156:16:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_0b99e75f7873826fa8b33e45f5babad412309f79417b9b0000257bd68fa2b8a3", + "typeString": "literal_string \"approve_failed\"" + }, + "value": "approve_failed" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_0b99e75f7873826fa8b33e45f5babad412309f79417b9b0000257bd68fa2b8a3", + "typeString": "literal_string \"approve_failed\"" + } + ], + "id": 1037, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "5092:7:3", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1047, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5092:81:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1048, + "nodeType": "ExpressionStatement", + "src": "5092:81:3" + }, + { + "assignments": [ + 1050 + ], + "declarations": [ + { + "constant": false, + "id": 1050, + "mutability": "mutable", + "name": "bridgeMode", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1123, + "src": "5183:17:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "typeName": { + "id": 1049, + "name": "bytes4", + "nodeType": "ElementaryTypeName", + "src": "5183:6:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1054, + "initialValue": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "expression": { + "argumentTypes": null, + "id": 1051, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 704, + "src": "5203:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 1052, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "getBridgeMode", + "nodeType": "MemberAccess", + "referencedDeclaration": 2967, + "src": "5203:28:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_pure$__$returns$_t_bytes4_$", + "typeString": "function () pure external returns (bytes4)" + } + }, + "id": 1053, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5203:30:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "5183:50:3" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "id": 1057, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1055, + "name": "bridgeMode", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1050, + "src": "5435:10:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30786231353136633236", + "id": 1056, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5449:10:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_2974903334_by_1", + "typeString": "int_const 2974903334" + }, + "value": "0xb1516c26" + }, + "src": "5435:24:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + }, + "id": 1073, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1071, + "name": "bridgeMode", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1050, + "src": "5581:10:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes4", + "typeString": "bytes4" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30783736353935623536", + "id": 1072, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5595:10:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1985567574_by_1", + "typeString": "int_const 1985567574" + }, + "value": "0x76595b56" + }, + "src": "5581:24:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": { + "id": 1087, + "nodeType": "Block", + "src": "5706:54:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "756e6b6e6f776e5f6272696467655f6d6f6465", + "id": 1084, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5727:21:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_445d91fc79f5d76a26706ce2f1c866b8b57fad4e5d19ffc80c8c136306007969", + "typeString": "literal_string \"unknown_bridge_mode\"" + }, + "value": "unknown_bridge_mode" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_445d91fc79f5d76a26706ce2f1c866b8b57fad4e5d19ffc80c8c136306007969", + "typeString": "literal_string \"unknown_bridge_mode\"" + } + ], + "id": 1083, + "name": "revert", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -19, + -19 + ], + "referencedDeclaration": -19, + "src": "5720:6:3", + "typeDescriptions": { + "typeIdentifier": "t_function_revert_pure$_t_string_memory_ptr_$returns$__$", + "typeString": "function (string memory) pure" + } + }, + "id": 1085, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5720:29:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1086, + "nodeType": "ExpressionStatement", + "src": "5720:29:3" + } + ] + }, + "id": 1088, + "nodeType": "IfStatement", + "src": "5578:182:3", + "trueBody": { + "id": 1082, + "nodeType": "Block", + "src": "5606:87:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1077, + "name": "sidechainAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 919, + "src": "5647:16:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_address_$", + "typeString": "function () view returns (address)" + } + }, + "id": 1078, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5647:18:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1079, + "name": "memberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1003, + "src": "5667:14:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1074, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 704, + "src": "5620:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 1076, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "relayTokens", + "nodeType": "MemberAccess", + "referencedDeclaration": 2962, + "src": "5620:26:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256) external" + } + }, + "id": 1080, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5620:62:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1081, + "nodeType": "ExpressionStatement", + "src": "5620:62:3" + } + ] + } + }, + "id": 1089, + "nodeType": "IfStatement", + "src": "5432:328:3", + "trueBody": { + "id": 1070, + "nodeType": "Block", + "src": "5461:103:3", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1063, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 710, + "src": "5510:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + ], + "id": 1062, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "5502:7:3", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1061, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "5502:7:3", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1064, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5502:14:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1065, + "name": "sidechainAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 919, + "src": "5518:16:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_address_$", + "typeString": "function () view returns (address)" + } + }, + "id": 1066, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5518:18:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1067, + "name": "memberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1003, + "src": "5538:14:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1058, + "name": "token_mediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 704, + "src": "5475:14:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ITokenMediator_$2968", + "typeString": "contract ITokenMediator" + } + }, + "id": 1060, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "relayTokens", + "nodeType": "MemberAccess", + "referencedDeclaration": 2955, + "src": "5475:26:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,address,uint256) external" + } + }, + "id": 1068, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5475:78:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1069, + "nodeType": "ExpressionStatement", + "src": "5475:78:3" + } + ] + } + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1094, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1091, + "name": "unaccountedTokens", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 970, + "src": "5824:17:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", + "typeString": "function () view returns (uint256)" + } + }, + "id": 1092, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5824:19:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1093, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5847:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "5824:24:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6e6f745f7472616e73666572726564", + "id": 1095, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5850:17:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_f8c2113376f862fd95528ef214ec6c4df8073d6ec53e0649e3a444e38c7f206c", + "typeString": "literal_string \"not_transferred\"" + }, + "value": "not_transferred" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_f8c2113376f862fd95528ef214ec6c4df8073d6ec53e0649e3a444e38c7f206c", + "typeString": "literal_string \"not_transferred\"" + } + ], + "id": 1090, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "5816:7:3", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1096, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5816:52:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1097, + "nodeType": "ExpressionStatement", + "src": "5816:52:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 1103, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1098, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 730, + "src": "5878:13:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1101, + "name": "memberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1003, + "src": "5912:14:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1099, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 730, + "src": "5894:13:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1100, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "5894:17:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1102, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5894:33:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "5878:49:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1104, + "nodeType": "ExpressionStatement", + "src": "5878:49:3" + }, + { + "assignments": [ + 1106 + ], + "declarations": [ + { + "constant": false, + "id": 1106, + "mutability": "mutable", + "name": "data", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1123, + "src": "5938:17:3", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 1105, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "5938:5:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1111, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "72656672657368526576656e75652829", + "id": 1109, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5982:18:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_331beb5f2892eb3ad8da75941af8f45f6a0e8580b91ecbb4c7fdf0219d1442a8", + "typeString": "literal_string \"refreshRevenue()\"" + }, + "value": "refreshRevenue()" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_331beb5f2892eb3ad8da75941af8f45f6a0e8580b91ecbb4c7fdf0219d1442a8", + "typeString": "literal_string \"refreshRevenue()\"" + } + ], + "expression": { + "argumentTypes": null, + "id": 1107, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "5958:3:3", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 1108, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encodeWithSignature", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "5958:23:3", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencodewithsignature_pure$_t_string_memory_ptr_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (string memory) pure returns (bytes memory)" + } + }, + "id": 1110, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5958:43:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "5938:63:3" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1115, + "name": "sidechainAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 919, + "src": "6036:16:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_address_$", + "typeString": "function () view returns (address)" + } + }, + "id": 1116, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6036:18:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1117, + "name": "data", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1106, + "src": "6056:4:3", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + { + "argumentTypes": null, + "id": 1118, + "name": "sidechain_maxgas", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 708, + "src": "6062:16:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1112, + "name": "amb", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 702, + "src": "6011:3:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IAMB_$2912", + "typeString": "contract IAMB" + } + }, + "id": 1114, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "requireToPassMessage", + "nodeType": "MemberAccess", + "referencedDeclaration": 2911, + "src": "6011:24:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_bytes_memory_ptr_$_t_uint256_$returns$_t_bytes32_$", + "typeString": "function (address,bytes memory,uint256) external returns (bytes32)" + } + }, + "id": 1119, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6011:68:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "id": 1120, + "nodeType": "ExpressionStatement", + "src": "6011:68:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 1121, + "name": "newTokens", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 976, + "src": "6096:9:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 974, + "id": 1122, + "nodeType": "Return", + "src": "6089:16:3" + } + ] + }, + "documentation": null, + "functionSelector": "2efc1007", + "id": 1124, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "sendTokensToBridge", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 971, + "nodeType": "ParameterList", + "parameters": [], + "src": "4534:2:3" + }, + "returnParameters": { + "id": 974, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 973, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1124, + "src": "4553:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 972, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4553:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4552:9:3" + }, + "scope": 1165, + "src": "4507:1605:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1163, + "nodeType": "Block", + "src": "6172:337:3", + "statements": [ + { + "assignments": [ + 1130 + ], + "declarations": [ + { + "constant": false, + "id": 1130, + "mutability": "mutable", + "name": "withdrawable", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1163, + "src": "6182:20:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1129, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "6182:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1133, + "initialValue": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1131, + "name": "adminFeesWithdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 952, + "src": "6205:21:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", + "typeString": "function () view returns (uint256)" + } + }, + "id": 1132, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6205:23:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "6182:46:3" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1136, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1134, + "name": "withdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1130, + "src": "6242:12:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1135, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "6258:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "6242:17:3", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 1139, + "nodeType": "IfStatement", + "src": "6238:31:3", + "trueBody": { + "expression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1137, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "6268:1:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "functionReturnParameters": 1128, + "id": 1138, + "nodeType": "Return", + "src": "6261:8:3" + } + }, + { + "expression": { + "argumentTypes": null, + "id": 1145, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1140, + "name": "totalAdminFeesWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 718, + "src": "6279:23:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1143, + "name": "withdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1130, + "src": "6333:12:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1141, + "name": "totalAdminFeesWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 718, + "src": "6305:23:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1142, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "6305:27:3", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1144, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6305:41:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "6279:67:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1146, + "nodeType": "ExpressionStatement", + "src": "6279:67:3" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1150, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "6379:5:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1151, + "name": "withdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1130, + "src": "6386:12:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1148, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 710, + "src": "6364:5:3", + "typeDescriptions": { + "typeIdentifier": "t_contract$_ERC20_$4148", + "typeString": "contract ERC20" + } + }, + "id": 1149, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "transfer", + "nodeType": "MemberAccess", + "referencedDeclaration": 3769, + "src": "6364:14:3", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", + "typeString": "function (address,uint256) external returns (bool)" + } + }, + "id": 1152, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6364:35:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "7472616e736665725f6661696c6564", + "id": 1153, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "6401:17:3", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_b8e9fee0fffb4680cf1dadd39ef447f97bcb04e1ca58d043770ea51a3c935e6c", + "typeString": "literal_string \"transfer_failed\"" + }, + "value": "transfer_failed" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_b8e9fee0fffb4680cf1dadd39ef447f97bcb04e1ca58d043770ea51a3c935e6c", + "typeString": "literal_string \"transfer_failed\"" + } + ], + "id": 1147, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "6356:7:3", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1154, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6356:63:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1155, + "nodeType": "ExpressionStatement", + "src": "6356:63:3" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1157, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "6453:5:3", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1158, + "name": "withdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1130, + "src": "6460:12:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1156, + "name": "AdminFeesWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 696, + "src": "6434:18:3", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 1159, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6434:39:3", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1160, + "nodeType": "EmitStatement", + "src": "6429:44:3" + }, + { + "expression": { + "argumentTypes": null, + "id": 1161, + "name": "withdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1130, + "src": "6490:12:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 1128, + "id": 1162, + "nodeType": "Return", + "src": "6483:19:3" + } + ] + }, + "documentation": null, + "functionSelector": "0419b45a", + "id": 1164, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdrawAdminFees", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1125, + "nodeType": "ParameterList", + "parameters": [], + "src": "6144:2:3" + }, + "returnParameters": { + "id": 1128, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1127, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1164, + "src": "6163:7:3", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1126, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "6163:7:3", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "6162:9:3" + }, + "scope": 1165, + "src": "6118:391:3", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + } + ], + "scope": 1166, + "src": "356:6155:3" + } + ], + "src": "0:6512:3" + }, + "compiler": { + "name": "solc", + "version": "0.6.6+commit.6c089d02.Emscripten.clang" + }, + "networks": {}, + "schemaVersion": "3.2.3", + "updatedAt": "2020-12-09T11:44:39.479Z", + "devdoc": { + "methods": { + "claimOwnership()": { + "details": "Allows the pendingOwner address to finalize the transfer." + }, + "setAdminFee(uint256)": { + "params": { + "newAdminFee": "fixed-point decimal in the same way as ether: 50% === 0.5 ether === \"500000000000000000\"" + } + }, + "transferOwnership(address)": { + "details": "Allows the current owner to set the pendingOwner address.", + "params": { + "newOwner": "The address to transfer ownership to." + } + } + } + }, + "userdoc": { + "methods": { + "setAdminFee(uint256)": { + "notice": "Admin fee as a fraction of revenue." + } + } + } +} \ No newline at end of file diff --git a/contracts/DataUnionSidechain.json b/contracts/DataUnionSidechain.json new file mode 100644 index 000000000..6d9526c99 --- /dev/null +++ b/contracts/DataUnionSidechain.json @@ -0,0 +1,36293 @@ +{ + "contractName": "DataUnionSidechain", + "abi": [ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "member", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "EarningsWithdrawn", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "agent", + "type": "address" + } + ], + "name": "JoinPartAgentAdded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "agent", + "type": "address" + } + ], + "name": "JoinPartAgentRemoved", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "member", + "type": "address" + } + ], + "name": "MemberJoined", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "member", + "type": "address" + } + ], + "name": "MemberParted", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "earningsPerMember", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "activeMemberCount", + "type": "uint256" + } + ], + "name": "NewEarnings", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amountWei", + "type": "uint256" + } + ], + "name": "NewMemberEthSent", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "RevenueReceived", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TransferToAddressInContract", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "from", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "TransferWithinContract", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "value", + "type": "uint256" + } + ], + "name": "UpdateNewMemberEth", + "type": "event" + }, + { + "inputs": [], + "name": "activeMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "claimOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "dataUnionMainnet", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "inactiveMemberCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "joinPartAgentCount", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "joinPartAgents", + "outputs": [ + { + "internalType": "enum DataUnionSidechain.ActiveStatus", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "lifetimeMemberEarnings", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "name": "memberData", + "outputs": [ + { + "internalType": "enum DataUnionSidechain.ActiveStatus", + "name": "status", + "type": "uint8" + }, + { + "internalType": "uint256", + "name": "earningsBeforeLastJoin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "lmeAtJoin", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "withdrawnEarnings", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "newMemberEth", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "pendingOwner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "token", + "outputs": [ + { + "internalType": "contract IERC677", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "tokenMediator", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalEarnings", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalEarningsWithdrawn", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "initialOwner", + "type": "address" + }, + { + "internalType": "address", + "name": "tokenAddress", + "type": "address" + }, + { + "internalType": "address[]", + "name": "initialJoinPartAgents", + "type": "address[]" + }, + { + "internalType": "address", + "name": "tokenMediatorAddress", + "type": "address" + }, + { + "internalType": "address", + "name": "mainnetDataUnionAddress", + "type": "address" + }, + { + "internalType": "uint256", + "name": "defaultNewMemberEth", + "type": "uint256" + } + ], + "name": "initialize", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "isInitialized", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "getStats", + "outputs": [ + { + "internalType": "uint256[6]", + "name": "", + "type": "uint256[6]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "val", + "type": "uint256" + } + ], + "name": "setNewMemberEth", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "member", + "type": "address" + } + ], + "name": "getEarnings", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "member", + "type": "address" + } + ], + "name": "getWithdrawn", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "member", + "type": "address" + } + ], + "name": "getWithdrawableEarnings", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "totalWithdrawable", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "agents", + "type": "address[]" + } + ], + "name": "addJoinPartAgents", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "agent", + "type": "address" + } + ], + "name": "addJoinPartAgent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "agent", + "type": "address" + } + ], + "name": "removeJoinPartAgent", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "refreshRevenue", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable", + "name": "member", + "type": "address" + } + ], + "name": "addMember", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "member", + "type": "address" + } + ], + "name": "partMember", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address payable[]", + "name": "members", + "type": "address[]" + } + ], + "name": "addMembers", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "members", + "type": "address[]" + } + ], + "name": "partMembers", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferToMemberInContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + } + ], + "name": "transferWithinContract", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "members", + "type": "address[]" + }, + { + "internalType": "bool", + "name": "sendToMainnet", + "type": "bool" + } + ], + "name": "withdrawMembers", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "member", + "type": "address" + }, + { + "internalType": "bool", + "name": "sendToMainnet", + "type": "bool" + } + ], + "name": "withdrawAll", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "member", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "sendToMainnet", + "type": "bool" + } + ], + "name": "withdraw", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "sendToMainnet", + "type": "bool" + } + ], + "name": "withdrawAllTo", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "sendToMainnet", + "type": "bool" + } + ], + "name": "withdrawTo", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "signer", + "type": "address" + }, + { + "internalType": "address", + "name": "recipient", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "signatureIsValid", + "outputs": [ + { + "internalType": "bool", + "name": "isValid", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromSigner", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "bool", + "name": "sendToMainnet", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "withdrawAllToSigned", + "outputs": [ + { + "internalType": "uint256", + "name": "withdrawn", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "fromSigner", + "type": "address" + }, + { + "internalType": "address", + "name": "to", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "sendToMainnet", + "type": "bool" + }, + { + "internalType": "bytes", + "name": "signature", + "type": "bytes" + } + ], + "name": "withdrawToSigned", + "outputs": [ + { + "internalType": "uint256", + "name": "withdrawn", + "type": "uint256" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "a", + "type": "address" + } + ], + "name": "toBytes", + "outputs": [ + { + "internalType": "bytes", + "name": "b", + "type": "bytes" + } + ], + "stateMutability": "pure", + "type": "function" + } + ], + "metadata": "{\"compiler\":{\"version\":\"0.6.6+commit.6c089d02\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"member\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"EarningsWithdrawn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"agent\",\"type\":\"address\"}],\"name\":\"JoinPartAgentAdded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"agent\",\"type\":\"address\"}],\"name\":\"JoinPartAgentRemoved\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"member\",\"type\":\"address\"}],\"name\":\"MemberJoined\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"member\",\"type\":\"address\"}],\"name\":\"MemberParted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"earningsPerMember\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"activeMemberCount\",\"type\":\"uint256\"}],\"name\":\"NewEarnings\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amountWei\",\"type\":\"uint256\"}],\"name\":\"NewMemberEthSent\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"RevenueReceived\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"TransferToAddressInContract\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"TransferWithinContract\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"UpdateNewMemberEth\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"activeMemberCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"agent\",\"type\":\"address\"}],\"name\":\"addJoinPartAgent\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"agents\",\"type\":\"address[]\"}],\"name\":\"addJoinPartAgents\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable\",\"name\":\"member\",\"type\":\"address\"}],\"name\":\"addMember\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address payable[]\",\"name\":\"members\",\"type\":\"address[]\"}],\"name\":\"addMembers\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"claimOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"dataUnionMainnet\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"member\",\"type\":\"address\"}],\"name\":\"getEarnings\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getStats\",\"outputs\":[{\"internalType\":\"uint256[6]\",\"name\":\"\",\"type\":\"uint256[6]\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"member\",\"type\":\"address\"}],\"name\":\"getWithdrawableEarnings\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"member\",\"type\":\"address\"}],\"name\":\"getWithdrawn\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"inactiveMemberCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"initialOwner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenAddress\",\"type\":\"address\"},{\"internalType\":\"address[]\",\"name\":\"initialJoinPartAgents\",\"type\":\"address[]\"},{\"internalType\":\"address\",\"name\":\"tokenMediatorAddress\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"mainnetDataUnionAddress\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"defaultNewMemberEth\",\"type\":\"uint256\"}],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"isInitialized\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"joinPartAgentCount\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"joinPartAgents\",\"outputs\":[{\"internalType\":\"enum DataUnionSidechain.ActiveStatus\",\"name\":\"\",\"type\":\"uint8\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"lifetimeMemberEarnings\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"name\":\"memberData\",\"outputs\":[{\"internalType\":\"enum DataUnionSidechain.ActiveStatus\",\"name\":\"status\",\"type\":\"uint8\"},{\"internalType\":\"uint256\",\"name\":\"earningsBeforeLastJoin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"lmeAtJoin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"withdrawnEarnings\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"newMemberEth\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"member\",\"type\":\"address\"}],\"name\":\"partMember\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"members\",\"type\":\"address[]\"}],\"name\":\"partMembers\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"refreshRevenue\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"agent\",\"type\":\"address\"}],\"name\":\"removeJoinPartAgent\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"val\",\"type\":\"uint256\"}],\"name\":\"setNewMemberEth\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"signer\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"name\":\"signatureIsValid\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"isValid\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"a\",\"type\":\"address\"}],\"name\":\"toBytes\",\"outputs\":[{\"internalType\":\"bytes\",\"name\":\"b\",\"type\":\"bytes\"}],\"stateMutability\":\"pure\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"token\",\"outputs\":[{\"internalType\":\"contract IERC677\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"tokenMediator\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalEarnings\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalEarningsWithdrawn\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"totalWithdrawable\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferToMemberInContract\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferWithinContract\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"member\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"sendToMainnet\",\"type\":\"bool\"}],\"name\":\"withdraw\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"member\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"sendToMainnet\",\"type\":\"bool\"}],\"name\":\"withdrawAll\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"sendToMainnet\",\"type\":\"bool\"}],\"name\":\"withdrawAllTo\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"fromSigner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"sendToMainnet\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"name\":\"withdrawAllToSigned\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"withdrawn\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"members\",\"type\":\"address[]\"},{\"internalType\":\"bool\",\"name\":\"sendToMainnet\",\"type\":\"bool\"}],\"name\":\"withdrawMembers\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"sendToMainnet\",\"type\":\"bool\"}],\"name\":\"withdrawTo\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"fromSigner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"sendToMainnet\",\"type\":\"bool\"},{\"internalType\":\"bytes\",\"name\":\"signature\",\"type\":\"bytes\"}],\"name\":\"withdrawToSigned\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"withdrawn\",\"type\":\"uint256\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}],\"devdoc\":{\"methods\":{\"claimOwnership()\":{\"details\":\"Allows the pendingOwner address to finalize the transfer.\"},\"signatureIsValid(address,address,uint256,bytes)\":{\"params\":{\"amount\":\"how much is authorized for withdraw, or zero for unlimited (withdrawAll)\",\"recipient\":\"of the tokens\",\"signature\":\"byte array from `web3.eth.accounts.sign`\",\"signer\":\"whose earnings are being withdrawn\"},\"returns\":{\"isValid\":\"true iff signer of the authorization (member whose earnings are going to be withdrawn) matches the signature\"}},\"transferOwnership(address)\":{\"details\":\"Allows the current owner to set the pendingOwner address.\",\"params\":{\"newOwner\":\"The address to transfer ownership to.\"}},\"transferWithinContract(address,uint256)\":{\"params\":{\"amount\":\"how much withdrawable earnings is transferred\",\"recipient\":\"whose withdrawable earnings will increase\"}},\"withdrawAllToSigned(address,address,bool,bytes)\":{\"params\":{\"fromSigner\":\"whose earnings are being withdrawn\",\"sendToMainnet\":\"if the tokens should be sent to mainnet or only withdrawn into sidechain address\",\"signature\":\"from the member, see `signatureIsValid` how signature generated for unlimited amount\",\"to\":\"the address the tokens will be sent to (instead of `msg.sender`)\"}},\"withdrawToSigned(address,address,uint256,bool,bytes)\":{\"params\":{\"amount\":\"of tokens to withdraw\",\"fromSigner\":\"whose earnings are being withdrawn\",\"sendToMainnet\":\"if the tokens should be sent to mainnet or only withdrawn into sidechain address\",\"signature\":\"from the member, see `signatureIsValid` how signature generated for unlimited amount\",\"to\":\"the address the tokens will be sent to (instead of `msg.sender`)\"}}}},\"userdoc\":{\"methods\":{\"getStats()\":{\"notice\":\"Atomic getter to get all state variables in one call This alleviates the fact that JSON RPC batch requests aren't available in ethers.js\"},\"refreshRevenue()\":{\"notice\":\"Process unaccounted tokens that have been sent previously Called by AMB (see DataUnionMainnet:sendTokensToBridge)\"},\"signatureIsValid(address,address,uint256,bytes)\":{\"notice\":\"Check signature from a member authorizing withdrawing its earnings to another account. Throws if the signature is badly formatted or doesn't match the given signer and amount. Signature has parts the act as replay protection: 1) `address(this)`: signature can't be used for other contracts; 2) `withdrawn[signer]`: signature only works once (for unspecified amount), and can be \\\"cancelled\\\" by sending a withdraw tx. Generated in Javascript with: `web3.eth.accounts.sign(recipientAddress + amount.toString(16, 64) + contractAddress.slice(2) + withdrawnTokens.toString(16, 64), signerPrivateKey)`, or for unlimited amount: `web3.eth.accounts.sign(recipientAddress + \\\"0\\\".repeat(64) + contractAddress.slice(2) + withdrawnTokens.toString(16, 64), signerPrivateKey)`.\"},\"transferToMemberInContract(address,uint256)\":{\"notice\":\"Transfer tokens from outside contract, add to a recipient's in-contract balance\"},\"transferWithinContract(address,uint256)\":{\"notice\":\"Transfer tokens from sender's in-contract balance to recipient's in-contract balance This is done by \\\"withdrawing\\\" sender's earnings and crediting them to recipient's unwithdrawn earnings, so withdrawnEarnings never decreases for anyone (within this function)\"},\"withdrawAllToSigned(address,address,bool,bytes)\":{\"notice\":\"Do an \\\"unlimited donate withdraw\\\" on behalf of someone else, to an address they've specified. Sponsored withdraw is paid by admin, but target account could be whatever the member specifies. The signature gives a \\\"blank cheque\\\" for admin to withdraw all tokens to `recipient` in the future, and it's valid until next withdraw (and so can be nullified by withdrawing any amount). A new signature needs to be obtained for each subsequent future withdraw.\"},\"withdrawToSigned(address,address,uint256,bool,bytes)\":{\"notice\":\"Do a \\\"donate withdraw\\\" on behalf of someone else, to an address they've specified. Sponsored withdraw is paid by admin, but target account could be whatever the member specifies. The signature is valid only for given amount of tokens that may be different from maximum withdrawable tokens.\"}}}},\"settings\":{\"compilationTarget\":{\"/home/heynow/streamr/data-union-solidity/contracts/DataUnionSidechain.sol\":\"DataUnionSidechain\"},\"evmVersion\":\"istanbul\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":200},\"remappings\":[]},\"sources\":{\"/home/heynow/streamr/data-union-solidity/contracts/DataUnionSidechain.sol\":{\"keccak256\":\"0x4642307a0866b24635ea7719ce499a371049ad8f0a87eb21c54cb829eb02b642\",\"urls\":[\"bzz-raw://2d5ea931bc6611d7fcce11f2d484aeef29e8e5a40e47e65aaa859dda6f4dacd1\",\"dweb:/ipfs/QmVwS7VYzQxuqwTwV3333ZmUztoq56CvuXZhLX2fbrB2Ai\"]},\"/home/heynow/streamr/data-union-solidity/contracts/IERC677.sol\":{\"keccak256\":\"0xe1eb10f511abe1922d4cac9b0c18b4119dc8096bcdfb0c53e2ad08d71ab193b4\",\"urls\":[\"bzz-raw://4b6846c8996809a49e275679d133ecff1b15aa2d8781bab9d6627b6c6bb4d1ed\",\"dweb:/ipfs/QmcYMawWrLWUVynAxEMK2dDCP6z4TbcAEyV7PYh8H3kuHh\"]},\"/home/heynow/streamr/data-union-solidity/contracts/Ownable.sol\":{\"keccak256\":\"0x0d96dac82f3bf17d2cdc90863a61c8957622c305bc3bbf9cd8f47facfa71dea1\",\"urls\":[\"bzz-raw://1cd621b81754be8073fc89bd2bc864e7b0169ba5f153edc5b9212a19f4890aa3\",\"dweb:/ipfs/QmcE5HTf3pptr2s7oiDqYtotCLrb736g8N7YyRZj8k5mHg\"]},\"openzeppelin-solidity/contracts/math/SafeMath.sol\":{\"keccak256\":\"0x9a9cf02622cd7a64261b10534fc3260449da25c98c9e96d1b4ae8110a20e5806\",\"urls\":[\"bzz-raw://2df142592d1dc267d9549049ee3317fa190d2f87eaa565f86ab05ec83f7ab8f5\",\"dweb:/ipfs/QmSkJtcfWo7c42KnL5hho6GFxK6HRNV91XABx1P7xDtfLV\"]},\"openzeppelin-solidity/contracts/token/ERC20/IERC20.sol\":{\"keccak256\":\"0x5c26b39d26f7ed489e555d955dcd3e01872972e71fdd1528e93ec164e4f23385\",\"urls\":[\"bzz-raw://efdc632af6960cf865dbc113665ea1f5b90eab75cc40ec062b2f6ae6da582017\",\"dweb:/ipfs/QmfAZFDuG62vxmAN9DnXApv7e7PMzPqi4RkqqZHLMSQiY5\"]}},\"version\":1}", + "bytecode": "0x608060405234801561001057600080fd5b50600080546001600160a01b0319169055612989806100306000396000f3fe6080604052600436106102605760003560e01c806371cdfd6811610144578063bf1e42c0116100b6578063ce7b78641161007a578063ce7b786414610d5e578063e30c397814610e31578063e6018c3114610e46578063ead5d35914610e70578063f2fde38b14610eb1578063fc0c546a14610ee457610267565b8063bf1e42c014610c5d578063c44b73a314610c72578063c59d484714610cc9578063ca6d56dc14610d16578063cc77244014610d4957610267565b80638da5cb5b116101085780638da5cb5b146109d35780639107d08e14610a04578063a2d3cf4b14610a70578063a4d6ddc014610b41578063ae66d94814610bf1578063b274bcc714610c2457610267565b806371cdfd681461088157806373e2290c146108ba57806379049017146108fb5780637b30ed431461091057806385a21246146109be57610267565b8063392e53cd116101dd5780634e71e0c8116101a15780634e71e0c8146106b9578063593b79fe146106ce5780635fb6c6ed14610776578063662d45a21461078b5780636d8018b8146107be5780636f4d469b146107d357610267565b8063392e53cd146105f85780633d8e36a3146106215780633ebff90e146106365780634bee91371461064b5780634e40ea641461068657610267565b80631a79246c116102245780631a79246c146104885780632b94411f146105605780632df3eba41461059b5780632e0d4212146105b0578063331beb5f146105e357610267565b8063015c7f511461026c5780630600a8651461034d57806309a6400b14610374578063131b9c04146103a75780631796621a146103da57610267565b3661026757005b600080fd5b34801561027857600080fd5b5061034b600480360360c081101561028f57600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b8111156102c257600080fd5b8201836020820111156102d457600080fd5b803590602001918460208302840111600160201b831117156102f557600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550506001600160a01b0383358116945060208401351692604001359150610ef99050565b005b34801561035957600080fd5b50610362610fe6565b60408051918252519081900360200190f35b34801561038057600080fd5b5061034b6004803603602081101561039757600080fd5b50356001600160a01b0316611005565b3480156103b357600080fd5b50610362600480360360208110156103ca57600080fd5b50356001600160a01b0316611125565b3480156103e657600080fd5b5061034b600480360360208110156103fd57600080fd5b810190602081018135600160201b81111561041757600080fd5b82018360208201111561042957600080fd5b803590602001918460208302840111600160201b8311171561044a57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506111d5945050505050565b34801561049457600080fd5b50610362600480360360a08110156104ab57600080fd5b6001600160a01b038235811692602081013590911691604082013591606081013515159181019060a081016080820135600160201b8111156104ec57600080fd5b8201836020820111156104fe57600080fd5b803590602001918460018302840111600160201b8311171561051f57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611254945050505050565b34801561056c57600080fd5b506103626004803603604081101561058357600080fd5b506001600160a01b03813516906020013515156112be565b3480156105a757600080fd5b506103626112da565b3480156105bc57600080fd5b50610362600480360360208110156105d357600080fd5b50356001600160a01b03166112e0565b3480156105ef57600080fd5b50610362611309565b34801561060457600080fd5b5061060d61147e565b604080519115158252519081900360200190f35b34801561062d57600080fd5b5061036261148f565b34801561064257600080fd5b50610362611495565b34801561065757600080fd5b506103626004803603604081101561066e57600080fd5b506001600160a01b038135169060200135151561149b565b34801561069257600080fd5b5061034b600480360360208110156106a957600080fd5b50356001600160a01b03166114b0565b3480156106c557600080fd5b5061034b611619565b3480156106da57600080fd5b50610701600480360360208110156106f157600080fd5b50356001600160a01b03166116cf565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561073b578181015183820152602001610723565b50505050905090810190601f1680156107685780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561078257600080fd5b506103626116f3565b34801561079757600080fd5b5061034b600480360360208110156107ae57600080fd5b50356001600160a01b03166116f9565b3480156107ca57600080fd5b5061036261181d565b3480156107df57600080fd5b5061034b600480360360208110156107f657600080fd5b810190602081018135600160201b81111561081057600080fd5b82018360208201111561082257600080fd5b803590602001918460208302840111600160201b8311171561084357600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550611823945050505050565b34801561088d57600080fd5b5061034b600480360360408110156108a457600080fd5b506001600160a01b0381351690602001356118bf565b3480156108c657600080fd5b50610362600480360360608110156108dd57600080fd5b506001600160a01b0381351690602081013590604001351515611990565b34801561090757600080fd5b506103626119a6565b34801561091c57600080fd5b5061034b6004803603602081101561093357600080fd5b810190602081018135600160201b81111561094d57600080fd5b82018360208201111561095f57600080fd5b803590602001918460208302840111600160201b8311171561098057600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506119ac945050505050565b3480156109ca57600080fd5b506103626119dc565b3480156109df57600080fd5b506109e86119e2565b604080516001600160a01b039092168252519081900360200190f35b348015610a1057600080fd5b50610a3760048036036020811015610a2757600080fd5b50356001600160a01b03166119f1565b60405180856002811115610a4757fe5b60ff16815260200184815260200183815260200182815260200194505050505060405180910390f35b348015610a7c57600080fd5b5061060d60048036036080811015610a9357600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b811115610acd57600080fd5b820183602082011115610adf57600080fd5b803590602001918460018302840111600160201b83111715610b0057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611a1c945050505050565b348015610b4d57600080fd5b5061036260048036036040811015610b6457600080fd5b810190602081018135600160201b811115610b7e57600080fd5b820183602082011115610b9057600080fd5b803590602001918460208302840111600160201b83111715610bb157600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295505050503515159050611c1d565b348015610bfd57600080fd5b5061036260048036036020811015610c1457600080fd5b50356001600160a01b0316611c6a565b348015610c3057600080fd5b5061034b60048036036040811015610c4757600080fd5b506001600160a01b038135169060200135611ce2565b348015610c6957600080fd5b506109e8611f58565b348015610c7e57600080fd5b50610ca560048036036020811015610c9557600080fd5b50356001600160a01b0316611f67565b60405180826002811115610cb557fe5b60ff16815260200191505060405180910390f35b348015610cd557600080fd5b50610cde611f7c565b604051808260c080838360005b83811015610d03578181015183820152602001610ceb565b5050505090500191505060405180910390f35b348015610d2257600080fd5b5061034b60048036036020811015610d3957600080fd5b50356001600160a01b0316611fc2565b348015610d5557600080fd5b506109e86121c8565b348015610d6a57600080fd5b5061036260048036036080811015610d8157600080fd5b6001600160a01b038235811692602081013590911691604082013515159190810190608081016060820135600160201b811115610dbd57600080fd5b820183602082011115610dcf57600080fd5b803590602001918460018302840111600160201b83111715610df057600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506121d7945050505050565b348015610e3d57600080fd5b506109e8612249565b348015610e5257600080fd5b5061034b60048036036020811015610e6957600080fd5b5035612258565b348015610e7c57600080fd5b5061036260048036036060811015610e9357600080fd5b506001600160a01b03813516906020810135906040013515156122ee565b348015610ebd57600080fd5b5061034b60048036036020811015610ed457600080fd5b50356001600160a01b0316612363565b348015610ef057600080fd5b506109e86123d0565b610f0161147e565b15610f53576040805162461bcd60e51b815260206004820152601860248201527f6572726f725f616c7265616479496e697469616c697a65640000000000000000604482015290519081900360640190fd5b60008054336001600160a01b031991821617909155600280549091166001600160a01b038716179055610f85846111d5565b600380546001600160a01b038086166001600160a01b0319928316179092556004805492851692909116919091179055610fbe81612258565b5050600080546001600160a01b0319166001600160a01b039590951694909417909355505050565b6000610fff6006546005546123df90919063ffffffff16565b90505b90565b6000546001600160a01b03163314611050576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b60016001600160a01b0382166000908152600d602052604090205460ff16600281111561107957fe5b146110c2576040805162461bcd60e51b8152602060048201526014602482015273195c9c9bdc97db9bdd1058dd1a5d995059d95b9d60621b604482015290519081900360640190fd5b6001600160a01b0381166000818152600d6020526040808220805460ff19166002179055517feac6c7d5a1c157497119a5d4f661d5f23b844c415452ef440ed346bd127d885e9190a2600a5461111f90600163ffffffff6123df16565b600a5550565b6001600160a01b0381166000908152600c6020526040812081815460ff16600281111561114e57fe5b1415611193576040805162461bcd60e51b815260206004820152600f60248201526e32b93937b92fb737ba26b2b6b132b960891b604482015290519081900360640190fd5b6001815460ff1660028111156111a557fe5b146111b15760006111c8565b60028101546009546111c89163ffffffff6123df16565b6001909101540192915050565b6000546001600160a01b03163314611220576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b60005b81518110156112505761124882828151811061123b57fe5b60200260200101516116f9565b600101611223565b5050565b600061126286868685611a1c565b6112a8576040805162461bcd60e51b81526020600482015260126024820152716572726f725f6261645369676e617475726560701b604482015290519081900360640190fd5b6112b486868686612421565b9695505050505050565b60006112d3836112cd336112e0565b84611990565b9392505050565b60055481565b60006113036112ee83611c6a565b6112f784611125565b9063ffffffff6123df16565b92915050565b600254604080516370a0823160e01b8152306004820152905160009283926001600160a01b03909116916370a0823191602480820192602092909190829003018186803b15801561135957600080fd5b505afa15801561136d573d6000803e3d6000fd5b505050506040513d602081101561138357600080fd5b5051905060006113a1611394610fe6565b839063ffffffff6123df16565b90508015806113b05750600754155b156113c057600092505050611002565b60006113d76007548361273290919063ffffffff16565b6009549091506113ed908263ffffffff61277416565b600955600554611403908363ffffffff61277416565b6005556040805183815290517f41b06c6e0a1531dcb4b86d53ec6268666aa12d55775f8e5a63596fc935cdcc229181900360200190a160075460408051838152602081019290925280517f24a9873073eba764d17ef9fa7475b3b209c02e6e6f7ed991c9c80e09226a37a79281900390910190a15091505090565b6002546001600160a01b0316151590565b60085481565b600a5481565b60006112d3836114aa856112e0565b846122ee565b336001600160a01b03821614806114e457506001336000908152600d602052604090205460ff1660028111156114e257fe5b145b61152a576040805162461bcd60e51b8152602060048201526012602482015271195c9c9bdc97db9bdd14195c9b5a5d1d195960721b604482015290519081900360640190fd5b6001600160a01b0381166000908152600c602052604090206001815460ff16600281111561155457fe5b1461159e576040805162461bcd60e51b815260206004820152601560248201527432b93937b92fb737ba20b1ba34bb32a6b2b6b132b960591b604482015290519081900360640190fd5b6115a782611125565b600182810191909155815460ff191660021782556007546115c7916123df565b6007556008546115de90600163ffffffff61277416565b6008556040516001600160a01b038316907f7df2bff504799b36cafb9574b3fcfd8432ef4a1fa89d1ba9fe40324501adf5f590600090a25050565b6001546001600160a01b0316331461166b576040805162461bcd60e51b815260206004820152601060248201526f37b7363ca832b73234b733a7bbb732b960811b604482015290519081900360640190fd5b600154600080546040516001600160a01b0393841693909116917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a360018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b604080516001600160a01b0392909216600560a21b18601483015260348201905290565b60075481565b6000546001600160a01b03163314611744576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b60016001600160a01b0382166000908152600d602052604090205460ff16600281111561176d57fe5b14156117c0576040805162461bcd60e51b815260206004820152601860248201527f6572726f725f616c72656164794163746976654167656e740000000000000000604482015290519081900360640190fd5b6001600160a01b0381166000818152600d6020526040808220805460ff19166001179055517f10581818fb1ffbfd9ac8500cba931a30c3a57b5e9b7972f2fa0aef002b3fde2b9190a2600a5461111f90600163ffffffff61277416565b60095481565b6001336000908152600d602052604090205460ff16600281111561184357fe5b1461188f576040805162461bcd60e51b8152602060048201526017602482015276195c9c9bdc97dbdb9b1e529bda5b94185c9d1059d95b9d604a1b604482015290519081900360640190fd5b60005b8151811015611250576118b78282815181106118aa57fe5b6020026020010151611fc2565b600101611892565b806118c9336112e0565b1015611918576040805162461bcd60e51b81526020600482015260196024820152786572726f725f696e73756666696369656e7442616c616e636560381b604482015290519081900360640190fd5b336000908152600c60205260409020600381015461193c908363ffffffff61277416565b600382015561194b83836127ce565b6040805183815290516001600160a01b0385169133917f638ce96e87261f007ef5c0389bb59b90db3e19c42edee859d6b09739d8d79f7f9181900360200190a3505050565b600061199e33858585612421565b949350505050565b600b5481565b60005b8151811015611250576119d48282815181106119c757fe5b60200260200101516114b0565b6001016119af565b60065481565b6000546001600160a01b031681565b600c60205260009081526040902080546001820154600283015460039093015460ff90921692909184565b60008151604114611a74576040805162461bcd60e51b815260206004820152601860248201527f6572726f725f6261645369676e61747572654c656e6774680000000000000000604482015290519081900360640190fd5b60208201516040830151606084015160001a601b811015611a9357601b015b8060ff16601b1480611aa857508060ff16601c145b611af9576040805162461bcd60e51b815260206004820152601960248201527f6572726f725f6261645369676e617475726556657273696f6e00000000000000604482015290519081900360640190fd5b6000878730611b078c611c6a565b60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a313034000000815250601d01856001600160a01b03166001600160a01b031660601b8152601401848152602001836001600160a01b03166001600160a01b031660601b8152601401828152602001945050505050604051602081830303815290604052805190602001209050600060018284878760405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015611bf5573d6000803e3d6000fd5b5050604051601f1901516001600160a01b038c81169116149650505050505050949350505050565b600080805b8451811015611c6257611c58611c4b868381518110611c3d57fe5b60200260200101518661149b565b839063ffffffff61277416565b9150600101611c22565b509392505050565b6001600160a01b0381166000908152600c6020526040812081815460ff166002811115611c9357fe5b1415611cd8576040805162461bcd60e51b815260206004820152600f60248201526e32b93937b92fb737ba26b2b6b132b960891b604482015290519081900360640190fd5b6003015492915050565b600254604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b158015611d2d57600080fd5b505afa158015611d41573d6000803e3d6000fd5b505050506040513d6020811015611d5757600080fd5b5051600254604080516323b872dd60e01b81523360048201523060248201526044810186905290519293506001600160a01b03909116916323b872dd916064808201926020929091908290030181600087803b158015611db657600080fd5b505af1158015611dca573d6000803e3d6000fd5b505050506040513d6020811015611de057600080fd5b5051611e24576040805162461bcd60e51b815260206004820152600e60248201526d32b93937b92fba3930b739b332b960911b604482015290519081900360640190fd5b600254604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b158015611e6f57600080fd5b505afa158015611e83573d6000803e3d6000fd5b505050506040513d6020811015611e9957600080fd5b5051905082611eae828463ffffffff6123df16565b1015611ef2576040805162461bcd60e51b815260206004820152600e60248201526d32b93937b92fba3930b739b332b960911b604482015290519081900360640190fd5b611efc84846127ce565b600554611f0f908463ffffffff61277416565b6005556040805184815290516001600160a01b0386169133917f4e018df3c92158645fcf45007db7029d3fa97d269866be2bd4360c5f5a6163e49181900360200190a350505050565b6004546001600160a01b031681565b600d6020526000908152604090205460ff1681565b611f84612935565b6040518060c0016040528060055481526020016006548152602001600754815260200160085481526020016009548152602001600a54815250905090565b6001336000908152600d602052604090205460ff166002811115611fe257fe5b1461202e576040805162461bcd60e51b8152602060048201526017602482015276195c9c9bdc97dbdb9b1e529bda5b94185c9d1059d95b9d604a1b604482015290519081900360640190fd5b6001600160a01b0381166000908152600c602052604090206001815460ff16600281111561205857fe5b14156120a1576040805162461bcd60e51b815260206004820152601360248201527232b93937b92fb0b63932b0b23ca6b2b6b132b960691b604482015290519081900360640190fd5b6002815460ff1660028111156120b357fe5b14156120d1576008546120cd90600163ffffffff6123df16565b6008555b600080825460ff1660028111156120e457fe5b1480156120f25750600b5415155b80156121005750600b544710155b825460ff19166001908117845560095460028501556007549192506121259190612774565b6007556040516001600160a01b038416907f0abf3b3f643594d958297062a019458e27d7766629590ac621aa1000fa1298ab90600090a280156121c357600b546040516001600160a01b0385169180156108fc02916000818181858888f19350505050156121c357600b5460408051918252517f55e2724f03f2711a94cf86d8b10c57130b103d6c2f1726076fbf9430340d41e79181900360200190a15b505050565b6003546001600160a01b031681565b60006121e68585600085611a1c565b61222c576040805162461bcd60e51b81526020600482015260126024820152716572726f725f6261645369676e617475726560701b604482015290519081900360640190fd5b612240858561223a886112e0565b86612421565b95945050505050565b6001546001600160a01b031681565b6000546001600160a01b031633146122a3576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b600b548114156122b2576122eb565b600b8190556040805182815290517f749d0aa4ca45d6142166deb1820b64a888996311bb9f74a88c081f5b041d949c9181900360200190a15b50565b6000336001600160a01b038516148061231157506000546001600160a01b031633145b612357576040805162461bcd60e51b8152602060048201526012602482015271195c9c9bdc97db9bdd14195c9b5a5d1d195960721b604482015290519081900360640190fd5b61199e84858585612421565b6000546001600160a01b031633146123ae576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6002546001600160a01b031681565b60006112d383836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250612839565b6000826124305750600061199e565b612439856112e0565b831115612489576040805162461bcd60e51b81526020600482015260196024820152786572726f725f696e73756666696369656e7442616c616e636560381b604482015290519081900360640190fd5b6001600160a01b0385166000908152600c6020526040902060038101546124b6908563ffffffff61277416565b60038201556006546124ce908563ffffffff61277416565b6006558215612625576002546003546001600160a01b0391821691634000aea09116866124fa896116cf565b6040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b0316815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561256457818101518382015260200161254c565b50505050905090810190601f1680156125915780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b1580156125b257600080fd5b505af11580156125c6573d6000803e3d6000fd5b505050506040513d60208110156125dc57600080fd5b5051612620576040805162461bcd60e51b815260206004820152600e60248201526d32b93937b92fba3930b739b332b960911b604482015290519081900360640190fd5b6126e9565b6002546040805163a9059cbb60e01b81526001600160a01b038881166004830152602482018890529151919092169163a9059cbb9160448083019260209291908290030181600087803b15801561267b57600080fd5b505af115801561268f573d6000803e3d6000fd5b505050506040513d60208110156126a557600080fd5b50516126e9576040805162461bcd60e51b815260206004820152600e60248201526d32b93937b92fba3930b739b332b960911b604482015290519081900360640190fd5b6040805185815290516001600160a01b038816917f48dc35af7b45e2a81fffad55f6e2fafacdb1d3d0d50d24ebdc16324f5ba757f1919081900360200190a25091949350505050565b60006112d383836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f0000000000008152506128d0565b6000828201838110156112d3576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b6001600160a01b0382166000908152600c6020526040902060018101546127fb908363ffffffff61277416565b60018201556000815460ff16600281111561281257fe5b14156121c357805460ff19166002178155600854612831906001612774565b600855505050565b600081848411156128c85760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561288d578181015183820152602001612875565b50505050905090810190601f1680156128ba5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b6000818361291f5760405162461bcd60e51b815260206004820181815283516024840152835190928392604490910191908501908083836000831561288d578181015183820152602001612875565b50600083858161292b57fe5b0495945050505050565b6040518060c00160405280600690602082028036833750919291505056fea26469706673582212201bc1ebe49687dbeb26b9f260d089c5aa418b9a9ef5e8f22bf1ce619e57f6f06c64736f6c63430006060033", + "deployedBytecode": "0x6080604052600436106102605760003560e01c806371cdfd6811610144578063bf1e42c0116100b6578063ce7b78641161007a578063ce7b786414610d5e578063e30c397814610e31578063e6018c3114610e46578063ead5d35914610e70578063f2fde38b14610eb1578063fc0c546a14610ee457610267565b8063bf1e42c014610c5d578063c44b73a314610c72578063c59d484714610cc9578063ca6d56dc14610d16578063cc77244014610d4957610267565b80638da5cb5b116101085780638da5cb5b146109d35780639107d08e14610a04578063a2d3cf4b14610a70578063a4d6ddc014610b41578063ae66d94814610bf1578063b274bcc714610c2457610267565b806371cdfd681461088157806373e2290c146108ba57806379049017146108fb5780637b30ed431461091057806385a21246146109be57610267565b8063392e53cd116101dd5780634e71e0c8116101a15780634e71e0c8146106b9578063593b79fe146106ce5780635fb6c6ed14610776578063662d45a21461078b5780636d8018b8146107be5780636f4d469b146107d357610267565b8063392e53cd146105f85780633d8e36a3146106215780633ebff90e146106365780634bee91371461064b5780634e40ea641461068657610267565b80631a79246c116102245780631a79246c146104885780632b94411f146105605780632df3eba41461059b5780632e0d4212146105b0578063331beb5f146105e357610267565b8063015c7f511461026c5780630600a8651461034d57806309a6400b14610374578063131b9c04146103a75780631796621a146103da57610267565b3661026757005b600080fd5b34801561027857600080fd5b5061034b600480360360c081101561028f57600080fd5b6001600160a01b038235811692602081013590911691810190606081016040820135600160201b8111156102c257600080fd5b8201836020820111156102d457600080fd5b803590602001918460208302840111600160201b831117156102f557600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550506001600160a01b0383358116945060208401351692604001359150610ef99050565b005b34801561035957600080fd5b50610362610fe6565b60408051918252519081900360200190f35b34801561038057600080fd5b5061034b6004803603602081101561039757600080fd5b50356001600160a01b0316611005565b3480156103b357600080fd5b50610362600480360360208110156103ca57600080fd5b50356001600160a01b0316611125565b3480156103e657600080fd5b5061034b600480360360208110156103fd57600080fd5b810190602081018135600160201b81111561041757600080fd5b82018360208201111561042957600080fd5b803590602001918460208302840111600160201b8311171561044a57600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506111d5945050505050565b34801561049457600080fd5b50610362600480360360a08110156104ab57600080fd5b6001600160a01b038235811692602081013590911691604082013591606081013515159181019060a081016080820135600160201b8111156104ec57600080fd5b8201836020820111156104fe57600080fd5b803590602001918460018302840111600160201b8311171561051f57600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611254945050505050565b34801561056c57600080fd5b506103626004803603604081101561058357600080fd5b506001600160a01b03813516906020013515156112be565b3480156105a757600080fd5b506103626112da565b3480156105bc57600080fd5b50610362600480360360208110156105d357600080fd5b50356001600160a01b03166112e0565b3480156105ef57600080fd5b50610362611309565b34801561060457600080fd5b5061060d61147e565b604080519115158252519081900360200190f35b34801561062d57600080fd5b5061036261148f565b34801561064257600080fd5b50610362611495565b34801561065757600080fd5b506103626004803603604081101561066e57600080fd5b506001600160a01b038135169060200135151561149b565b34801561069257600080fd5b5061034b600480360360208110156106a957600080fd5b50356001600160a01b03166114b0565b3480156106c557600080fd5b5061034b611619565b3480156106da57600080fd5b50610701600480360360208110156106f157600080fd5b50356001600160a01b03166116cf565b6040805160208082528351818301528351919283929083019185019080838360005b8381101561073b578181015183820152602001610723565b50505050905090810190601f1680156107685780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561078257600080fd5b506103626116f3565b34801561079757600080fd5b5061034b600480360360208110156107ae57600080fd5b50356001600160a01b03166116f9565b3480156107ca57600080fd5b5061036261181d565b3480156107df57600080fd5b5061034b600480360360208110156107f657600080fd5b810190602081018135600160201b81111561081057600080fd5b82018360208201111561082257600080fd5b803590602001918460208302840111600160201b8311171561084357600080fd5b919080806020026020016040519081016040528093929190818152602001838360200280828437600092019190915250929550611823945050505050565b34801561088d57600080fd5b5061034b600480360360408110156108a457600080fd5b506001600160a01b0381351690602001356118bf565b3480156108c657600080fd5b50610362600480360360608110156108dd57600080fd5b506001600160a01b0381351690602081013590604001351515611990565b34801561090757600080fd5b506103626119a6565b34801561091c57600080fd5b5061034b6004803603602081101561093357600080fd5b810190602081018135600160201b81111561094d57600080fd5b82018360208201111561095f57600080fd5b803590602001918460208302840111600160201b8311171561098057600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295506119ac945050505050565b3480156109ca57600080fd5b506103626119dc565b3480156109df57600080fd5b506109e86119e2565b604080516001600160a01b039092168252519081900360200190f35b348015610a1057600080fd5b50610a3760048036036020811015610a2757600080fd5b50356001600160a01b03166119f1565b60405180856002811115610a4757fe5b60ff16815260200184815260200183815260200182815260200194505050505060405180910390f35b348015610a7c57600080fd5b5061060d60048036036080811015610a9357600080fd5b6001600160a01b03823581169260208101359091169160408201359190810190608081016060820135600160201b811115610acd57600080fd5b820183602082011115610adf57600080fd5b803590602001918460018302840111600160201b83111715610b0057600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250929550611a1c945050505050565b348015610b4d57600080fd5b5061036260048036036040811015610b6457600080fd5b810190602081018135600160201b811115610b7e57600080fd5b820183602082011115610b9057600080fd5b803590602001918460208302840111600160201b83111715610bb157600080fd5b9190808060200260200160405190810160405280939291908181526020018383602002808284376000920191909152509295505050503515159050611c1d565b348015610bfd57600080fd5b5061036260048036036020811015610c1457600080fd5b50356001600160a01b0316611c6a565b348015610c3057600080fd5b5061034b60048036036040811015610c4757600080fd5b506001600160a01b038135169060200135611ce2565b348015610c6957600080fd5b506109e8611f58565b348015610c7e57600080fd5b50610ca560048036036020811015610c9557600080fd5b50356001600160a01b0316611f67565b60405180826002811115610cb557fe5b60ff16815260200191505060405180910390f35b348015610cd557600080fd5b50610cde611f7c565b604051808260c080838360005b83811015610d03578181015183820152602001610ceb565b5050505090500191505060405180910390f35b348015610d2257600080fd5b5061034b60048036036020811015610d3957600080fd5b50356001600160a01b0316611fc2565b348015610d5557600080fd5b506109e86121c8565b348015610d6a57600080fd5b5061036260048036036080811015610d8157600080fd5b6001600160a01b038235811692602081013590911691604082013515159190810190608081016060820135600160201b811115610dbd57600080fd5b820183602082011115610dcf57600080fd5b803590602001918460018302840111600160201b83111715610df057600080fd5b91908080601f0160208091040260200160405190810160405280939291908181526020018383808284376000920191909152509295506121d7945050505050565b348015610e3d57600080fd5b506109e8612249565b348015610e5257600080fd5b5061034b60048036036020811015610e6957600080fd5b5035612258565b348015610e7c57600080fd5b5061036260048036036060811015610e9357600080fd5b506001600160a01b03813516906020810135906040013515156122ee565b348015610ebd57600080fd5b5061034b60048036036020811015610ed457600080fd5b50356001600160a01b0316612363565b348015610ef057600080fd5b506109e86123d0565b610f0161147e565b15610f53576040805162461bcd60e51b815260206004820152601860248201527f6572726f725f616c7265616479496e697469616c697a65640000000000000000604482015290519081900360640190fd5b60008054336001600160a01b031991821617909155600280549091166001600160a01b038716179055610f85846111d5565b600380546001600160a01b038086166001600160a01b0319928316179092556004805492851692909116919091179055610fbe81612258565b5050600080546001600160a01b0319166001600160a01b039590951694909417909355505050565b6000610fff6006546005546123df90919063ffffffff16565b90505b90565b6000546001600160a01b03163314611050576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b60016001600160a01b0382166000908152600d602052604090205460ff16600281111561107957fe5b146110c2576040805162461bcd60e51b8152602060048201526014602482015273195c9c9bdc97db9bdd1058dd1a5d995059d95b9d60621b604482015290519081900360640190fd5b6001600160a01b0381166000818152600d6020526040808220805460ff19166002179055517feac6c7d5a1c157497119a5d4f661d5f23b844c415452ef440ed346bd127d885e9190a2600a5461111f90600163ffffffff6123df16565b600a5550565b6001600160a01b0381166000908152600c6020526040812081815460ff16600281111561114e57fe5b1415611193576040805162461bcd60e51b815260206004820152600f60248201526e32b93937b92fb737ba26b2b6b132b960891b604482015290519081900360640190fd5b6001815460ff1660028111156111a557fe5b146111b15760006111c8565b60028101546009546111c89163ffffffff6123df16565b6001909101540192915050565b6000546001600160a01b03163314611220576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b60005b81518110156112505761124882828151811061123b57fe5b60200260200101516116f9565b600101611223565b5050565b600061126286868685611a1c565b6112a8576040805162461bcd60e51b81526020600482015260126024820152716572726f725f6261645369676e617475726560701b604482015290519081900360640190fd5b6112b486868686612421565b9695505050505050565b60006112d3836112cd336112e0565b84611990565b9392505050565b60055481565b60006113036112ee83611c6a565b6112f784611125565b9063ffffffff6123df16565b92915050565b600254604080516370a0823160e01b8152306004820152905160009283926001600160a01b03909116916370a0823191602480820192602092909190829003018186803b15801561135957600080fd5b505afa15801561136d573d6000803e3d6000fd5b505050506040513d602081101561138357600080fd5b5051905060006113a1611394610fe6565b839063ffffffff6123df16565b90508015806113b05750600754155b156113c057600092505050611002565b60006113d76007548361273290919063ffffffff16565b6009549091506113ed908263ffffffff61277416565b600955600554611403908363ffffffff61277416565b6005556040805183815290517f41b06c6e0a1531dcb4b86d53ec6268666aa12d55775f8e5a63596fc935cdcc229181900360200190a160075460408051838152602081019290925280517f24a9873073eba764d17ef9fa7475b3b209c02e6e6f7ed991c9c80e09226a37a79281900390910190a15091505090565b6002546001600160a01b0316151590565b60085481565b600a5481565b60006112d3836114aa856112e0565b846122ee565b336001600160a01b03821614806114e457506001336000908152600d602052604090205460ff1660028111156114e257fe5b145b61152a576040805162461bcd60e51b8152602060048201526012602482015271195c9c9bdc97db9bdd14195c9b5a5d1d195960721b604482015290519081900360640190fd5b6001600160a01b0381166000908152600c602052604090206001815460ff16600281111561155457fe5b1461159e576040805162461bcd60e51b815260206004820152601560248201527432b93937b92fb737ba20b1ba34bb32a6b2b6b132b960591b604482015290519081900360640190fd5b6115a782611125565b600182810191909155815460ff191660021782556007546115c7916123df565b6007556008546115de90600163ffffffff61277416565b6008556040516001600160a01b038316907f7df2bff504799b36cafb9574b3fcfd8432ef4a1fa89d1ba9fe40324501adf5f590600090a25050565b6001546001600160a01b0316331461166b576040805162461bcd60e51b815260206004820152601060248201526f37b7363ca832b73234b733a7bbb732b960811b604482015290519081900360640190fd5b600154600080546040516001600160a01b0393841693909116917f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e091a360018054600080546001600160a01b03199081166001600160a01b03841617909155169055565b604080516001600160a01b0392909216600560a21b18601483015260348201905290565b60075481565b6000546001600160a01b03163314611744576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b60016001600160a01b0382166000908152600d602052604090205460ff16600281111561176d57fe5b14156117c0576040805162461bcd60e51b815260206004820152601860248201527f6572726f725f616c72656164794163746976654167656e740000000000000000604482015290519081900360640190fd5b6001600160a01b0381166000818152600d6020526040808220805460ff19166001179055517f10581818fb1ffbfd9ac8500cba931a30c3a57b5e9b7972f2fa0aef002b3fde2b9190a2600a5461111f90600163ffffffff61277416565b60095481565b6001336000908152600d602052604090205460ff16600281111561184357fe5b1461188f576040805162461bcd60e51b8152602060048201526017602482015276195c9c9bdc97dbdb9b1e529bda5b94185c9d1059d95b9d604a1b604482015290519081900360640190fd5b60005b8151811015611250576118b78282815181106118aa57fe5b6020026020010151611fc2565b600101611892565b806118c9336112e0565b1015611918576040805162461bcd60e51b81526020600482015260196024820152786572726f725f696e73756666696369656e7442616c616e636560381b604482015290519081900360640190fd5b336000908152600c60205260409020600381015461193c908363ffffffff61277416565b600382015561194b83836127ce565b6040805183815290516001600160a01b0385169133917f638ce96e87261f007ef5c0389bb59b90db3e19c42edee859d6b09739d8d79f7f9181900360200190a3505050565b600061199e33858585612421565b949350505050565b600b5481565b60005b8151811015611250576119d48282815181106119c757fe5b60200260200101516114b0565b6001016119af565b60065481565b6000546001600160a01b031681565b600c60205260009081526040902080546001820154600283015460039093015460ff90921692909184565b60008151604114611a74576040805162461bcd60e51b815260206004820152601860248201527f6572726f725f6261645369676e61747572654c656e6774680000000000000000604482015290519081900360640190fd5b60208201516040830151606084015160001a601b811015611a9357601b015b8060ff16601b1480611aa857508060ff16601c145b611af9576040805162461bcd60e51b815260206004820152601960248201527f6572726f725f6261645369676e617475726556657273696f6e00000000000000604482015290519081900360640190fd5b6000878730611b078c611c6a565b60405160200180807f19457468657265756d205369676e6564204d6573736167653a0a313034000000815250601d01856001600160a01b03166001600160a01b031660601b8152601401848152602001836001600160a01b03166001600160a01b031660601b8152601401828152602001945050505050604051602081830303815290604052805190602001209050600060018284878760405160008152602001604052604051808581526020018460ff1660ff1681526020018381526020018281526020019450505050506020604051602081039080840390855afa158015611bf5573d6000803e3d6000fd5b5050604051601f1901516001600160a01b038c81169116149650505050505050949350505050565b600080805b8451811015611c6257611c58611c4b868381518110611c3d57fe5b60200260200101518661149b565b839063ffffffff61277416565b9150600101611c22565b509392505050565b6001600160a01b0381166000908152600c6020526040812081815460ff166002811115611c9357fe5b1415611cd8576040805162461bcd60e51b815260206004820152600f60248201526e32b93937b92fb737ba26b2b6b132b960891b604482015290519081900360640190fd5b6003015492915050565b600254604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b158015611d2d57600080fd5b505afa158015611d41573d6000803e3d6000fd5b505050506040513d6020811015611d5757600080fd5b5051600254604080516323b872dd60e01b81523360048201523060248201526044810186905290519293506001600160a01b03909116916323b872dd916064808201926020929091908290030181600087803b158015611db657600080fd5b505af1158015611dca573d6000803e3d6000fd5b505050506040513d6020811015611de057600080fd5b5051611e24576040805162461bcd60e51b815260206004820152600e60248201526d32b93937b92fba3930b739b332b960911b604482015290519081900360640190fd5b600254604080516370a0823160e01b815230600482015290516000926001600160a01b0316916370a08231916024808301926020929190829003018186803b158015611e6f57600080fd5b505afa158015611e83573d6000803e3d6000fd5b505050506040513d6020811015611e9957600080fd5b5051905082611eae828463ffffffff6123df16565b1015611ef2576040805162461bcd60e51b815260206004820152600e60248201526d32b93937b92fba3930b739b332b960911b604482015290519081900360640190fd5b611efc84846127ce565b600554611f0f908463ffffffff61277416565b6005556040805184815290516001600160a01b0386169133917f4e018df3c92158645fcf45007db7029d3fa97d269866be2bd4360c5f5a6163e49181900360200190a350505050565b6004546001600160a01b031681565b600d6020526000908152604090205460ff1681565b611f84612935565b6040518060c0016040528060055481526020016006548152602001600754815260200160085481526020016009548152602001600a54815250905090565b6001336000908152600d602052604090205460ff166002811115611fe257fe5b1461202e576040805162461bcd60e51b8152602060048201526017602482015276195c9c9bdc97dbdb9b1e529bda5b94185c9d1059d95b9d604a1b604482015290519081900360640190fd5b6001600160a01b0381166000908152600c602052604090206001815460ff16600281111561205857fe5b14156120a1576040805162461bcd60e51b815260206004820152601360248201527232b93937b92fb0b63932b0b23ca6b2b6b132b960691b604482015290519081900360640190fd5b6002815460ff1660028111156120b357fe5b14156120d1576008546120cd90600163ffffffff6123df16565b6008555b600080825460ff1660028111156120e457fe5b1480156120f25750600b5415155b80156121005750600b544710155b825460ff19166001908117845560095460028501556007549192506121259190612774565b6007556040516001600160a01b038416907f0abf3b3f643594d958297062a019458e27d7766629590ac621aa1000fa1298ab90600090a280156121c357600b546040516001600160a01b0385169180156108fc02916000818181858888f19350505050156121c357600b5460408051918252517f55e2724f03f2711a94cf86d8b10c57130b103d6c2f1726076fbf9430340d41e79181900360200190a15b505050565b6003546001600160a01b031681565b60006121e68585600085611a1c565b61222c576040805162461bcd60e51b81526020600482015260126024820152716572726f725f6261645369676e617475726560701b604482015290519081900360640190fd5b612240858561223a886112e0565b86612421565b95945050505050565b6001546001600160a01b031681565b6000546001600160a01b031633146122a3576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b600b548114156122b2576122eb565b600b8190556040805182815290517f749d0aa4ca45d6142166deb1820b64a888996311bb9f74a88c081f5b041d949c9181900360200190a15b50565b6000336001600160a01b038516148061231157506000546001600160a01b031633145b612357576040805162461bcd60e51b8152602060048201526012602482015271195c9c9bdc97db9bdd14195c9b5a5d1d195960721b604482015290519081900360640190fd5b61199e84858585612421565b6000546001600160a01b031633146123ae576040805162461bcd60e51b815260206004820152600960248201526837b7363ca7bbb732b960b91b604482015290519081900360640190fd5b600180546001600160a01b0319166001600160a01b0392909216919091179055565b6002546001600160a01b031681565b60006112d383836040518060400160405280601e81526020017f536166654d6174683a207375627472616374696f6e206f766572666c6f770000815250612839565b6000826124305750600061199e565b612439856112e0565b831115612489576040805162461bcd60e51b81526020600482015260196024820152786572726f725f696e73756666696369656e7442616c616e636560381b604482015290519081900360640190fd5b6001600160a01b0385166000908152600c6020526040902060038101546124b6908563ffffffff61277416565b60038201556006546124ce908563ffffffff61277416565b6006558215612625576002546003546001600160a01b0391821691634000aea09116866124fa896116cf565b6040518463ffffffff1660e01b815260040180846001600160a01b03166001600160a01b0316815260200183815260200180602001828103825283818151815260200191508051906020019080838360005b8381101561256457818101518382015260200161254c565b50505050905090810190601f1680156125915780820380516001836020036101000a031916815260200191505b50945050505050602060405180830381600087803b1580156125b257600080fd5b505af11580156125c6573d6000803e3d6000fd5b505050506040513d60208110156125dc57600080fd5b5051612620576040805162461bcd60e51b815260206004820152600e60248201526d32b93937b92fba3930b739b332b960911b604482015290519081900360640190fd5b6126e9565b6002546040805163a9059cbb60e01b81526001600160a01b038881166004830152602482018890529151919092169163a9059cbb9160448083019260209291908290030181600087803b15801561267b57600080fd5b505af115801561268f573d6000803e3d6000fd5b505050506040513d60208110156126a557600080fd5b50516126e9576040805162461bcd60e51b815260206004820152600e60248201526d32b93937b92fba3930b739b332b960911b604482015290519081900360640190fd5b6040805185815290516001600160a01b038816917f48dc35af7b45e2a81fffad55f6e2fafacdb1d3d0d50d24ebdc16324f5ba757f1919081900360200190a25091949350505050565b60006112d383836040518060400160405280601a81526020017f536166654d6174683a206469766973696f6e206279207a65726f0000000000008152506128d0565b6000828201838110156112d3576040805162461bcd60e51b815260206004820152601b60248201527f536166654d6174683a206164646974696f6e206f766572666c6f770000000000604482015290519081900360640190fd5b6001600160a01b0382166000908152600c6020526040902060018101546127fb908363ffffffff61277416565b60018201556000815460ff16600281111561281257fe5b14156121c357805460ff19166002178155600854612831906001612774565b600855505050565b600081848411156128c85760405162461bcd60e51b81526004018080602001828103825283818151815260200191508051906020019080838360005b8381101561288d578181015183820152602001612875565b50505050905090810190601f1680156128ba5780820380516001836020036101000a031916815260200191505b509250505060405180910390fd5b505050900390565b6000818361291f5760405162461bcd60e51b815260206004820181815283516024840152835190928392604490910191908501908083836000831561288d578181015183820152602001612875565b50600083858161292b57fe5b0495945050505050565b6040518060c00160405280600690602082028036833750919291505056fea26469706673582212201bc1ebe49687dbeb26b9f260d089c5aa418b9a9ef5e8f22bf1ce619e57f6f06c64736f6c63430006060033", + "immutableReferences": {}, + "sourceMap": "271:16899:4:-:0;;;2096:43;5:9:-1;2:2;;;27:1;24;17:12;2:2;-1:-1;2133:1:4;577:14:12;;-1:-1:-1;;;;;;577:14:12;;;271:16899:4;;;;;;", + "deployedSourceMap": "271:16899:4:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12:1:-1;9;2:12;2180:713:4;;5:9:-1;2:2;;;27:1;24;17:12;2:2;2180:713:4;;;;;;15:3:-1;10;7:12;4:2;;;32:1;29;22:12;4:2;-1:-1;;;;;2180:713:4;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;11:28;;8:2;;;52:1;49;42:12;8:2;2180:713:4;;41:9:-1;34:4;18:14;14:25;11:40;8:2;;;64:1;61;54:12;8:2;2180:713:4;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;-1:-1;;;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;2180:713:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;2180:713:4;;-1:-1:-1;;;;;;;2180:713:4;;;;;-1:-1:-1;2180:713:4;;;;;;;;;;-1:-1:-1;2180:713:4;;-1:-1:-1;2180:713:4:i;:::-;;4469:124;;5:9:-1;2:2;;;27:1;24;17:12;2:2;4469:124:4;;;:::i;:::-;;;;;;;;;;;;;;;;5097:308;;5:9:-1;2:2;;;27:1;24;17:12;2:2;5097:308:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;5097:308:4;-1:-1:-1;;;;;5097:308:4;;:::i;3641:424::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;3641:424:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;3641:424:4;-1:-1:-1;;;;;3641:424:4;;:::i;4599:181::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;4599:181:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;4599:181:4;;;;;;;;-1:-1:-1;;;11:28;;8:2;;;52:1;49;42:12;8:2;4599:181:4;;41:9:-1;34:4;18:14;14:25;11:40;8:2;;;64:1;61;54:12;8:2;4599:181:4;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;-1:-1;;;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;4599:181:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;4599:181:4;;-1:-1:-1;4599:181:4;;-1:-1:-1;;;;;4599:181:4:i;15415:377::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;15415:377:4;;;;;;15:3:-1;10;7:12;4:2;;;32:1;29;22:12;4:2;-1:-1;;;;;15415:377:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;11:28;;8:2;;;52:1;49;42:12;8:2;15415:377:4;;41:9:-1;34:4;18:14;14:25;11:40;8:2;;;64:1;61;54:12;8:2;15415:377:4;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;-1:-1;;;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;15415:377:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;15415:377:4;;-1:-1:-1;15415:377:4;;-1:-1:-1;;;;;15415:377:4:i;10807:190::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;10807:190:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;;;;;;10807:190:4;;;;;;;;;;:::i;1518:28::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1518:28:4;;;:::i;4315:148::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;4315:148:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;4315:148:4;-1:-1:-1;;;;;4315:148:4;;:::i;5555:593::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;5555:593:4;;;:::i;2899:103::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;2899:103:4;;;:::i;:::-;;;;;;;;;;;;;;;;;;1634:34;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1634:34:4;;;:::i;1718:33::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1718:33:4;;;:::i;10337:190::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;10337:190:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;;;;;;10337:190:4;;;;;;;;;;:::i;6993:547::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;6993:547:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;6993:547:4;-1:-1:-1;;;;;6993:547:4;;:::i;1123:226:12:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1123:226:12;;;:::i;15798:389:4:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;15798:389:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;15798:389:4;-1:-1:-1;;;;;15798:389:4;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:100:-1;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;15798:389:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1596:32;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1596:32:4;;;:::i;4786:305::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;4786:305:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;4786:305:4;-1:-1:-1;;;;;4786:305:4;;:::i;1674:37::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1674:37:4;;;:::i;7546:186::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;7546:186:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;7546:186:4;;;;;;;;-1:-1:-1;;;11:28;;8:2;;;52:1;49;42:12;8:2;7546:186:4;;41:9:-1;34:4;18:14;14:25;11:40;8:2;;;64:1;61;54:12;8:2;7546:186:4;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;-1:-1;;;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;7546:186:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;7546:186:4;;-1:-1:-1;7546:186:4;;-1:-1:-1;;;;;7546:186:4:i;9012:470::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;9012:470:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;;;;;;9012:470:4;;;;;;;;:::i;11003:182::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;11003:182:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;;;;;;11003:182:4;;;;;;;;;;;;;;;:::i;1758:27::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1758:27:4;;;:::i;7773:162::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;7773:162:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;7773:162:4;;;;;;;;-1:-1:-1;;;11:28;;8:2;;;52:1;49;42:12;8:2;7773:162:4;;41:9:-1;34:4;18:14;14:25;11:40;8:2;;;64:1;61;54:12;8:2;7773:162:4;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;-1:-1;;;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;7773:162:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;7773:162:4;;-1:-1:-1;7773:162:4;;-1:-1:-1;;;;;7773:162:4:i;1552:37::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1552:37:4;;;:::i;236:20:12:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;236:20:12;;;:::i;:::-;;;;-1:-1:-1;;;;;236:20:12;;;;;;;;;;;;;;1792:48:4;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1792:48:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;1792:48:4;-1:-1:-1;;;;;1792:48:4;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;12403:1022;;5:9:-1;2:2;;;27:1;24;17:12;2:2;12403:1022:4;;;;;;15:3:-1;10;7:12;4:2;;;32:1;29;22:12;4:2;-1:-1;;;;;12403:1022:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;11:28;;8:2;;;52:1;49;42:12;8:2;12403:1022:4;;41:9:-1;34:4;18:14;14:25;11:40;8:2;;;64:1;61;54:12;8:2;12403:1022:4;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;-1:-1;;;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;12403:1022:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;12403:1022:4;;-1:-1:-1;12403:1022:4;;-1:-1:-1;;;;;12403:1022:4:i;10007:324::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;10007:324:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;10007:324:4;;;;;;;;-1:-1:-1;;;11:28;;8:2;;;52:1;49;42:12;8:2;10007:324:4;;41:9:-1;34:4;18:14;14:25;11:40;8:2;;;64:1;61;54:12;8:2;10007:324:4;;;;;;101:9:-1;95:2;81:12;77:21;67:8;63:36;60:51;-1:-1;;;25:12;22:29;11:108;8:2;;;132:1;129;122:12;8:2;10007:324:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;10007:324:4;;-1:-1:-1;;;;10007:324:4;;;;-1:-1:-1;10007:324:4;:::i;4071:238::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;4071:238:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;4071:238:4;-1:-1:-1;;;;;4071:238:4;;:::i;8044:530::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;8044:530:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;;;;;;8044:530:4;;;;;;;;:::i;1480:31::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1480:31:4;;;:::i;1846:54::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1846:54:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;1846:54:4;-1:-1:-1;;;;;1846:54:4;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;3175:289;;5:9:-1;2:2;;;27:1;24;17:12;2:2;3175:289:4;;;:::i;:::-;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;3175:289:4;;;;;;;;;;;;;;;;6154:833;;5:9:-1;2:2;;;27:1;24;17:12;2:2;6154:833:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;6154:833:4;-1:-1:-1;;;;;6154:833:4;;:::i;1446:28::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1446:28:4;;;:::i;14296:383::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;14296:383:4;;;;;;15:3:-1;10;7:12;4:2;;;32:1;29;22:12;4:2;-1:-1;;;;;14296:383:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;11:28;;8:2;;;52:1;49;42:12;8:2;14296:383:4;;41:9:-1;34:4;18:14;14:25;11:40;8:2;;;64:1;61;54:12;8:2;14296:383:4;;;;;;100:9:-1;95:1;81:12;77:20;67:8;63:35;60:50;-1:-1;;;25:12;22:29;11:107;8:2;;;131:1;128;121:12;8:2;14296:383:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;30:3:-1;22:6;14;1:33;99:1;81:16;;74:27;;;;-1:-1;14296:383:4;;-1:-1:-1;14296:383:4;;-1:-1:-1;;;;;14296:383:4:i;262:27:12:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;262:27:12;;;:::i;3470:165:4:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;3470:165:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;3470:165:4;;:::i;10533:268::-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;10533:268:4;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;;;;;;10533:268:4;;;;;;;;;;;;;;;:::i;929:102:12:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;929:102:12;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;929:102:12;-1:-1:-1;;;;;929:102:12;;:::i;1420:20:4:-;;5:9:-1;2:2;;;27:1;24;17:12;2:2;1420:20:4;;;:::i;2180:713::-;2456:15;:13;:15::i;:::-;2455:16;2447:53;;;;;-1:-1:-1;;;2447:53:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;2510:5;:18;;2518:10;-1:-1:-1;;;;;;2510:18:4;;;;;;;2633:5;:29;;;;;-1:-1:-1;;;;;2633:29:4;;;;;2672:40;2690:21;2672:17;:40::i;:::-;2722:13;:36;;-1:-1:-1;;;;;2722:36:4;;;-1:-1:-1;;;;;;2722:36:4;;;;;;;2768:16;:42;;;;;;;;;;;;;;;2820:36;2836:19;2820:15;:36::i;:::-;-1:-1:-1;;2866:5:4;:20;;-1:-1:-1;;;;;;2866:20:4;-1:-1:-1;;;;;2866:20:4;;;;;;;;;;;-1:-1:-1;;;2180:713:4:o;4469:124::-;4519:7;4545:41;4563:22;;4545:13;;:17;;:41;;;;:::i;:::-;4538:48;;4469:124;;:::o;5097:308::-;739:5:12;;-1:-1:-1;;;;;739:5:12;725:10;:19;717:41;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;;;;5201:19:4::1;-1:-1:-1::0;;;;;5176:21:4;::::1;;::::0;;;:14:::1;:21;::::0;;;;;::::1;;:44;::::0;::::1;;;;;;;5168:77;;;::::0;;-1:-1:-1;;;5168:77:4;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;5168:77:4;;;;;;;;;;;;;::::1;;-1:-1:-1::0;;;;;5255:21:4;::::1;;::::0;;;:14:::1;:21;::::0;;;;;:45;;-1:-1:-1;;5255:45:4::1;5279:21;5255:45;::::0;;5315:27;::::1;::::0;5255:21;5315:27:::1;5373:18;::::0;:25:::1;::::0;5396:1:::1;5373:25;:22;:25;:::i;:::-;5352:18;:46:::0;-1:-1:-1;5097:308:4:o;3641:424::-;-1:-1:-1;;;;;3744:18:4;;3699:7;3744:18;;;:10;:18;;;;;3699:7;3780:11;;;;:32;;;;;;;;;;3772:60;;;;;-1:-1:-1;;;3772:60:4;;;;;;;;;;;;-1:-1:-1;;;3772:60:4;;;;;;;;;;;;;;;3936:19;3921:11;;;;:34;;;;;;;;;:123;;4043:1;3921:123;;;4005:14;;;;3978:22;;:42;;;:26;:42;:::i;:::-;3861:27;;;;;:197;;3641:424;-1:-1:-1;;3641:424:4:o;4599:181::-;739:5:12;;-1:-1:-1;;;;;739:5:12;725:10;:19;717:41;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;;;;4683:9:4::1;4678:96;4702:6;:13;4698:1;:17;4678:96;;;4736:27;4753:6;4760:1;4753:9;;;;;;;;;;;;;;4736:16;:27::i;:::-;4717:3;;4678:96;;;;4599:181:::0;:::o;15415:377::-;15608:14;15646:51;15663:10;15675:2;15679:6;15687:9;15646:16;:51::i;:::-;15638:82;;;;;-1:-1:-1;;;15638:82:4;;;;;;;;;;;;-1:-1:-1;;;15638:82:4;;;;;;;;;;;;;;;15737:48;15747:10;15759:2;15763:6;15771:13;15737:9;:48::i;:::-;15730:55;15415:377;-1:-1:-1;;;;;;15415:377:4:o;10807:190::-;10894:7;10924:66;10935:2;10939:35;10963:10;10939:23;:35::i;:::-;10976:13;10924:10;:66::i;:::-;10917:73;10807:190;-1:-1:-1;;;10807:190:4:o;1518:28::-;;;;:::o;4315:148::-;4385:7;4411:45;4435:20;4448:6;4435:12;:20::i;:::-;4411:19;4423:6;4411:11;:19::i;:::-;:23;:45;:23;:45;:::i;:::-;4404:52;4315:148;-1:-1:-1;;4315:148:4:o;5555:593::-;5634:5;;:30;;;-1:-1:-1;;;5634:30:4;;5658:4;5634:30;;;;;;5597:7;;;;-1:-1:-1;;;;;5634:5:4;;;;:15;;:30;;;;;;;;;;;;;;;:5;:30;;;2:2:-1;;;;27:1;24;17:12;2:2;5634:30:4;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;5634:30:4;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;5634:30:4;;-1:-1:-1;5674:15:4;5692:32;5704:19;:17;:19::i;:::-;5692:7;;:32;:11;:32;:::i;:::-;5674:50;-1:-1:-1;5766:12:4;;;:38;;-1:-1:-1;5782:17:4;;:22;5766:38;5762:52;;;5813:1;5806:8;;;;;;5762:52;5824:25;5852:30;5864:17;;5852:7;:11;;:30;;;;:::i;:::-;5917:22;;5824:58;;-1:-1:-1;5917:45:4;;5824:58;5917:45;:26;:45;:::i;:::-;5892:22;:70;5988:13;;:26;;6006:7;5988:26;:17;:26;:::i;:::-;5972:13;:42;6029:24;;;;;;;;;;;;;;;;;6099:17;;6068:49;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;6134:7:4;-1:-1:-1;;5555:593:4;:::o;2899:103::-;2975:5;;-1:-1:-1;;;;;2975:5:4;2967:28;;2899:103;:::o;1634:34::-;;;;:::o;1718:33::-;;;;:::o;10337:190::-;10426:7;10456:64;10465:6;10473:31;10497:6;10473:23;:31::i;:::-;10506:13;10456:8;:64::i;6993:547::-;7054:10;-1:-1:-1;;;;;7054:20:4;;;;:73;;-1:-1:-1;7108:19:4;7093:10;7078:26;;;;:14;:26;;;;;;;;:49;;;;;;;;;7054:73;7046:104;;;;;-1:-1:-1;;;7046:104:4;;;;;;;;;;;;-1:-1:-1;;;7046:104:4;;;;;;;;;;;;;;;-1:-1:-1;;;;;7186:18:4;;7160:23;7186:18;;;:10;:18;;;;;7237:19;7222:11;;;;:34;;;;;;;;;7214:68;;;;;-1:-1:-1;;;7214:68:4;;;;;;;;;;;;-1:-1:-1;;;7214:68:4;;;;;;;;;;;;;;;7322:19;7334:6;7322:11;:19::i;:::-;7292:27;;;;:49;;;;7351:35;;-1:-1:-1;;7351:35:4;7365:21;7351:35;;;7416:17;;:24;;:21;:24::i;:::-;7396:17;:44;7472:19;;:26;;7496:1;7472:26;:23;:26;:::i;:::-;7450:19;:48;7513:20;;-1:-1:-1;;;;;7513:20:4;;;;;;;;6993:547;;:::o;1123:226:12:-;1188:12;;-1:-1:-1;;;;;1188:12:12;1174:10;:26;1166:55;;;;;-1:-1:-1;;;1166:55:12;;;;;;;;;;;;-1:-1:-1;;;1166:55:12;;;;;;;;;;;;;;;1264:12;;;1257:5;;1236:41;;-1:-1:-1;;;;;1264:12:12;;;;1257:5;;;;1236:41;;;1295:12;;;;1287:20;;-1:-1:-1;;;;;;1287:20:12;;;-1:-1:-1;;;;;1295:12:12;;1287:20;;;;1317:25;;;1123:226::o;15798:389:4:-;15911:4;15905:11;;-1:-1:-1;;;;;15934:50:4;;;;-1:-1:-1;;;16049:52:4;16028:2;16021:10;;15997:118;16148:2;16141:10;;16128:24;;15905:11;15882:299::o;1596:32::-;;;;:::o;4786:305::-;739:5:12;;-1:-1:-1;;;;;739:5:12;725:10;:19;717:41;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;;;;4887:19:4::1;-1:-1:-1::0;;;;;4862:21:4;::::1;;::::0;;;:14:::1;:21;::::0;;;;;::::1;;:44;::::0;::::1;;;;;;;;4854:81;;;::::0;;-1:-1:-1;;;4854:81:4;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;::::1;::::0;;;;;;;;;;;;;::::1;;-1:-1:-1::0;;;;;4945:21:4;::::1;;::::0;;;:14:::1;:21;::::0;;;;;:43;;-1:-1:-1;;4945:43:4::1;4969:19;4945:43;::::0;;5003:25;::::1;::::0;4945:21;5003:25:::1;5059:18;::::0;:25:::1;::::0;5082:1:::1;5059:25;:22;:25;:::i;1674:37::-:0;;;;:::o;7546:186::-;1984:19;1969:10;1954:26;;;;:14;:26;;;;;;;;:49;;;;;;;;;1946:85;;;;;-1:-1:-1;;;1946:85:4;;;;;;;;;;;;-1:-1:-1;;;1946:85:4;;;;;;;;;;;;;;;7640:9:::1;7635:91;7659:7;:14;7655:1;:18;7635:91;;;7694:21;7704:7;7712:1;7704:10;;;;;;;;;;;;;;7694:9;:21::i;:::-;7675:3;;7635:91;;9012:470:::0;9140:6;9101:35;9125:10;9101:23;:35::i;:::-;:45;;9093:83;;;;;-1:-1:-1;;;9093:83:4;;;;;;;;;;;;-1:-1:-1;;;9093:83:4;;;;;;;;;;;;;;;9282:10;9245:23;9271:22;;;:10;:22;;;;;9328;;;;:34;;9355:6;9328:34;:26;:34;:::i;:::-;9303:22;;;:59;9372:35;9389:9;9400:6;9372:16;:35::i;:::-;9422:53;;;;;;;;-1:-1:-1;;;;;9422:53:4;;;9445:10;;9422:53;;;;;;;;;9012:470;;;:::o;11003:182::-;11100:7;11130:48;11140:10;11152:2;11156:6;11164:13;11130:9;:48::i;:::-;11123:55;11003:182;-1:-1:-1;;;;11003:182:4:o;1758:27::-;;;;:::o;7773:162::-;7842:9;7837:92;7861:7;:14;7857:1;:18;7837:92;;;7896:22;7907:7;7915:1;7907:10;;;;;;;;;;;;;;7896;:22::i;:::-;7877:3;;7837:92;;1552:37;;;;:::o;236:20:12:-;;;-1:-1:-1;;;;;236:20:12;;:::o;1792:48:4:-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::o;12403:1022::-;12576:12;12612:9;:16;12632:2;12612:22;12604:59;;;;;-1:-1:-1;;;12604:59:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;12818:2;12803:18;;12797:25;12861:2;12846:18;;12840:25;12912:2;12897:18;;12891:25;12674:9;12883:34;12944:2;12940:6;;12936:44;;;12967:2;12962:7;12936:44;12997:1;:7;;13002:2;12997:7;:18;;;;13008:1;:7;;13013:2;13008:7;12997:18;12989:56;;;;;-1:-1:-1;;;12989:56:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;13151:19;13250:9;13261:6;13277:4;13284:20;13297:6;13284:12;:20::i;:::-;13183:122;;;;;;;;;;;;;-1:-1:-1;;;;;13183:122:4;-1:-1:-1;;;;;13183:122:4;;;;;;;;;;;;;-1:-1:-1;;;;;13183:122:4;-1:-1:-1;;;;;13183:122:4;;;;;;;;;;;;;;;;;;;;49:4:-1;39:7;30;26:21;22:32;13:7;6:49;13183:122:4;;;13173:133;;;;;;13151:155;;13316:24;13343:31;13353:11;13366:1;13369;13372;13343:31;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;-1:-1;;13343:31:4;;-1:-1:-1;;13343:31:4;;-1:-1:-1;;;;;13392:26:4;;;;;;;-1:-1:-1;;;;;;;12403:1022:4;;;;;;:::o;10007:324::-;10110:7;;;10164:135;10188:7;:14;10184:1;:18;10164:135;;;10235:53;10249:38;10261:7;10269:1;10261:10;;;;;;;;;;;;;;10273:13;10249:11;:38::i;:::-;10235:9;;:53;:13;:53;:::i;:::-;10223:65;-1:-1:-1;10204:3:4;;10164:135;;;-1:-1:-1;10315:9:4;10007:324;-1:-1:-1;;;10007:324:4:o;4071:238::-;-1:-1:-1;;;;;4175:18:4;;4130:7;4175:18;;;:10;:18;;;;;4130:7;4211:11;;;;:32;;;;;;;;;;4203:60;;;;;-1:-1:-1;;;4203:60:4;;;;;;;;;;;;-1:-1:-1;;;4203:60:4;;;;;;;;;;;;;;;4280:22;;;;4071:238;-1:-1:-1;;4071:238:4:o;8044:530::-;8147:5;;:30;;;-1:-1:-1;;;8147:30:4;;8171:4;8147:30;;;;;;8129:15;;-1:-1:-1;;;;;8147:5:4;;:15;;:30;;;;;;;;;;;;;;:5;:30;;;2:2:-1;;;;27:1;24;17:12;2:2;8147:30:4;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;8147:30:4;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;8147:30:4;8195:5;;:53;;;-1:-1:-1;;;8195:53:4;;8214:10;8195:53;;;;8234:4;8195:53;;;;;;;;;;;;8147:30;;-1:-1:-1;;;;;;8195:5:4;;;;:18;;:53;;;;;8147:30;;8195:53;;;;;;;;:5;;:53;;;2:2:-1;;;;27:1;24;17:12;2:2;8195:53:4;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;8195:53:4;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;8195:53:4;8187:80;;;;;-1:-1:-1;;;8187:80:4;;;;;;;;;;;;-1:-1:-1;;;8187:80:4;;;;;;;;;;;;;;;8294:5;;:30;;;-1:-1:-1;;;8294:30:4;;8318:4;8294:30;;;;;;8277:14;;-1:-1:-1;;;;;8294:5:4;;:15;;:30;;;;;;;;;;;;;;:5;:30;;;2:2:-1;;;;27:1;24;17:12;2:2;8294:30:4;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;8294:30:4;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;8294:30:4;;-1:-1:-1;8371:6:4;8342:25;8294:30;8356:10;8342:25;:13;:25;:::i;:::-;:35;;8334:62;;;;;-1:-1:-1;;;8334:62:4;;;;;;;;;;;;-1:-1:-1;;;8334:62:4;;;;;;;;;;;;;;;8407:35;8424:9;8435:6;8407:16;:35::i;:::-;8468:13;;:25;;8486:6;8468:25;:17;:25;:::i;:::-;8452:13;:41;8508:59;;;;;;;;-1:-1:-1;;;;;8508:59:4;;;8536:10;;8508:59;;;;;;;;;8044:530;;;;:::o;1480:31::-;;;-1:-1:-1;;;;;1480:31:4;;:::o;1846:54::-;;;;;;;;;;;;;;;:::o;3175:289::-;3216:17;;:::i;:::-;3245:212;;;;;;;;3266:13;;3245:212;;;;3293:22;;3245:212;;;;3329:17;;3245:212;;;;3360:19;;3245:212;;;;3393:22;;3245:212;;;;3429:18;;3245:212;;;;;3175:289;:::o;6154:833::-;1984:19;1969:10;1954:26;;;;:14;:26;;;;;;;;:49;;;;;;;;;1946:85;;;;;-1:-1:-1;;;1946:85:4;;;;;;;;;;;;-1:-1:-1;;;1946:85:4;;;;;;;;;;;;;;;-1:-1:-1;;;;;6258:18:4;::::1;6232:23;6258:18:::0;;;:10:::1;:18;::::0;;;;6309:19:::1;6294:11:::0;;::::1;;:34;::::0;::::1;;;;;;;;6286:66;;;::::0;;-1:-1:-1;;;6286:66:4;;::::1;;::::0;::::1;::::0;::::1;::::0;;;;-1:-1:-1;;;6286:66:4;;;;;;;;;;;;;::::1;;6380:21;6365:11:::0;;::::1;;:36;::::0;::::1;;;;;;;6362:113;;;6438:19;::::0;:26:::1;::::0;6462:1:::1;6438:26;:23;:26;:::i;:::-;6416:19;:48:::0;6362:113:::1;6484:12;::::0;6499:11;;::::1;;:32;::::0;::::1;;;;;;;:53;;;;-1:-1:-1::0;6535:12:4::1;::::0;:17;::::1;6499:53;:94;;;;;6581:12;;6556:21;:37;;6499:94;6603:33:::0;;-1:-1:-1;;6603:33:4::1;6617:19;6603:33:::0;;::::1;::::0;;6663:22:::1;::::0;6646:14:::1;::::0;::::1;:39:::0;6715:17:::1;::::0;6484:109;;-1:-1:-1;6715:24:4::1;::::0;:17;:21:::1;:24::i;:::-;6695:17;:44:::0;6754:20:::1;::::0;-1:-1:-1;;;;;6754:20:4;::::1;::::0;::::1;::::0;;;::::1;6854:7;6850:131;;;6893:12;::::0;6881:25:::1;::::0;-1:-1:-1;;;;;6881:11:4;::::1;::::0;:25;::::1;;;::::0;::::1;::::0;;;6893:12;6881:11;:25;::::1;;;;;;6877:94;;;6943:12;::::0;6926:30:::1;::::0;;;;;;::::1;::::0;;;;::::1;::::0;;::::1;6877:94;2041:1;;6154:833:::0;:::o;1446:28::-;;;-1:-1:-1;;;;;1446:28:4;;:::o;14296:383::-;14471:14;14509:46;14526:10;14538:2;14542:1;14545:9;14509:16;:46::i;:::-;14501:77;;;;;-1:-1:-1;;;14501:77:4;;;;;;;;;;;;-1:-1:-1;;;14501:77:4;;;;;;;;;;;;;;;14595;14605:10;14617:2;14621:35;14645:10;14621:23;:35::i;:::-;14658:13;14595:9;:77::i;:::-;14588:84;14296:383;-1:-1:-1;;;;;14296:383:4:o;262:27:12:-;;;-1:-1:-1;;;;;262:27:12;;:::o;3470:165:4:-;739:5:12;;-1:-1:-1;;;;;739:5:12;725:10;:19;717:41;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;;;;3542:12:4::1;;3535:3;:19;3532:31;;;3556:7;;3532:31;3572:12;:18:::0;;;3605:23:::1;::::0;;;;;;;::::1;::::0;;;;::::1;::::0;;::::1;768:1:12;3470:165:4::0;:::o;10533:268::-;10632:7;10663:10;-1:-1:-1;;;;;10663:20:4;;;;:43;;-1:-1:-1;10701:5:4;;-1:-1:-1;;;;;10701:5:4;10687:10;:19;10663:43;10655:74;;;;;-1:-1:-1;;;10655:74:4;;;;;;;;;;;;-1:-1:-1;;;10655:74:4;;;;;;;;;;;;;;;10746:48;10756:6;10764;10772;10780:13;10746:9;:48::i;929:102:12:-;739:5;;-1:-1:-1;;;;;739:5:12;725:10;:19;717:41;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;-1:-1:-1;;;717:41:12;;;;;;;;;;;;;;;1001:12:::1;:23:::0;;-1:-1:-1;;;;;;1001:23:12::1;-1:-1:-1::0;;;;;1001:23:12;;;::::1;::::0;;;::::1;::::0;;929:102::o;1420:20:4:-;;;-1:-1:-1;;;;;1420:20:4;;:::o;1321:134:17:-;1379:7;1405:43;1409:1;1412;1405:43;;;;;;;;;;;;;;;;;:3;:43::i;16338:830:4:-;16450:7;16477:11;16473:25;;-1:-1:-1;16497:1:4;16490:8;;16473:25;16526:29;16550:4;16526:23;:29::i;:::-;16516:6;:39;;16508:77;;;;;-1:-1:-1;;;16508:77:4;;;;;;;;;;;;-1:-1:-1;;;16508:77:4;;;;;;;;;;;;;;;-1:-1:-1;;;;;16621:16:4;;16595:23;16621:16;;;:10;:16;;;;;16672:22;;;;:34;;16699:6;16672:34;:26;:34;:::i;:::-;16647:22;;;:59;16741:22;;:34;;16768:6;16741:34;:26;:34;:::i;:::-;16716:22;:59;16785:307;;;;16841:5;;16884:13;;-1:-1:-1;;;;;16841:5:4;;;;:21;;16884:13;16919:6;16947:11;16955:2;16947:7;:11::i;:::-;16841:135;;;;;;;;;;;;;-1:-1:-1;;;;;16841:135:4;-1:-1:-1;;;;;16841:135:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;16841:135:4;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5:9:-1;2:2;;;27:1;24;17:12;2:2;16841:135:4;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;16841:135:4;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;16841:135:4;16816:208;;;;;-1:-1:-1;;;16816:208:4;;;;;;;;;;;;-1:-1:-1;;;16816:208:4;;;;;;;;;;;;;;;16785:307;;;17047:5;;:26;;;-1:-1:-1;;;17047:26:4;;-1:-1:-1;;;;;17047:26:4;;;;;;;;;;;;;;;:5;;;;;:14;;:26;;;;;;;;;;;;;;:5;;:26;;;2:2:-1;;;;27:1;24;17:12;2:2;17047:26:4;;;;8:9:-1;5:2;;;45:16;42:1;39;24:38;77:16;74:1;67:27;5:2;17047:26:4;;;;;;;15:2:-1;10:3;7:11;4:2;;;31:1;28;21:12;4:2;-1:-1;17047:26:4;17039:53;;;;;-1:-1:-1;;;17039:53:4;;;;;;;;;;;;-1:-1:-1;;;17039:53:4;;;;;;;;;;;;;;;17107:31;;;;;;;;-1:-1:-1;;;;;17107:31:4;;;;;;;;;;;;;-1:-1:-1;17155:6:4;;16338:830;-1:-1:-1;;;;16338:830:4:o;3101:130:17:-;3159:7;3185:39;3189:1;3192;3185:39;;;;;;;;;;;;;;;;;:3;:39::i;874:176::-;932:7;963:5;;;986:6;;;;978:46;;;;;-1:-1:-1;;;978:46:17;;;;;;;;;;;;;;;;;;;;;;;;;;;9578:423:4;-1:-1:-1;;;;;9678:18:4;;9652:23;9678:18;;;:10;:18;;;;;9736:27;;;;:39;;9768:6;9736:39;:31;:39;:::i;:::-;9706:27;;;:69;9854:17;9839:11;;;;:32;;;;;;;;;9835:160;;;9887:35;;-1:-1:-1;;9887:35:4;9901:21;9887:35;;;9958:19;;:26;;9887:35;9958:23;:26::i;:::-;9936:19;:48;9578:423;;;:::o;1746:187:17:-;1832:7;1867:12;1859:6;;;;1851:29;;;;-1:-1:-1;;;1851:29:17;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;23:1:-1;8:100;33:3;30:1;27:10;8:100;;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;;12:14;1851:29:17;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;;1902:5:17;;;1746:187::o;3713:272::-;3799:7;3833:12;3826:5;3818:28;;;;-1:-1:-1;;;3818:28:17;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;27:10:-1;;8:100;;90:11;;;84:18;71:11;;;64:39;52:2;45:10;8:100;;3818:28:17;;3856:9;3872:1;3868;:5;;;;;;;3713:272;-1:-1:-1;;;;;3713:272:17:o;271:16899:4:-;;;;;;;;;;;29:2:-1;21:6;17:15;125:4;109:14;101:6;88:42;-1:-1;271:16899:4;;;-1:-1:-1;;271:16899:4:o", + "source": "pragma solidity 0.6.6;\n\nimport \"openzeppelin-solidity/contracts/math/SafeMath.sol\";\nimport \"openzeppelin-solidity/contracts/token/ERC20/IERC20.sol\";\nimport \"./IERC677.sol\";\nimport \"./Ownable.sol\"; // TODO: switch to \"openzeppelin-solidity/contracts/access/Ownable.sol\";\n\ncontract DataUnionSidechain is Ownable {\n using SafeMath for uint256;\n\n //used to describe members and join part agents\n enum ActiveStatus {None, Active, Inactive}\n\n //emitted by joins/parts\n event MemberJoined(address indexed member);\n event MemberParted(address indexed member);\n event JoinPartAgentAdded(address indexed agent);\n event JoinPartAgentRemoved(address indexed agent);\n\n //emitted when revenue received\n event RevenueReceived(uint256 amount);\n event NewEarnings(uint256 earningsPerMember, uint256 activeMemberCount);\n\n //emitted by withdrawal\n event EarningsWithdrawn(address indexed member, uint256 amount);\n\n //in-contract transfers\n event TransferWithinContract(address indexed from, address indexed to, uint amount);\n event TransferToAddressInContract(address indexed from, address indexed to, uint amount);\n\n //new member eth\n event UpdateNewMemberEth(uint value);\n event NewMemberEthSent(uint amountWei);\n\n struct MemberInfo {\n ActiveStatus status;\n uint256 earningsBeforeLastJoin;\n uint256 lmeAtJoin;\n uint256 withdrawnEarnings;\n }\n\n IERC677 public token;\n address public tokenMediator;\n address public dataUnionMainnet;\n\n uint256 public totalEarnings;\n uint256 public totalEarningsWithdrawn;\n\n uint256 public activeMemberCount;\n uint256 public inactiveMemberCount;\n uint256 public lifetimeMemberEarnings;\n\n uint256 public joinPartAgentCount;\n\n uint256 public newMemberEth;\n\n mapping(address => MemberInfo) public memberData;\n mapping(address => ActiveStatus) public joinPartAgents;\n\n modifier onlyJoinPartAgent() {\n require(joinPartAgents[msg.sender] == ActiveStatus.Active, \"error_onlyJoinPartAgent\");\n _;\n }\n\n // owner will be set by initialize()\n constructor() public Ownable(address(0)) {}\n\n receive() external payable {}\n\n function initialize(\n address initialOwner,\n address tokenAddress,\n address[] memory initialJoinPartAgents,\n address tokenMediatorAddress,\n address mainnetDataUnionAddress,\n uint256 defaultNewMemberEth\n ) public {\n require(!isInitialized(), \"error_alreadyInitialized\");\n owner = msg.sender; // set real owner at the end. During initialize, addJoinPartAgents can be called by owner only\n token = IERC677(tokenAddress);\n addJoinPartAgents(initialJoinPartAgents);\n tokenMediator = tokenMediatorAddress;\n dataUnionMainnet = mainnetDataUnionAddress;\n setNewMemberEth(defaultNewMemberEth);\n owner = initialOwner;\n }\n\n function isInitialized() public view returns (bool){\n return address(token) != address(0);\n }\n\n /**\n * Atomic getter to get all state variables in one call\n * This alleviates the fact that JSON RPC batch requests aren't available in ethers.js\n */\n function getStats() public view returns (uint256[6] memory) {\n return [\n totalEarnings,\n totalEarningsWithdrawn,\n activeMemberCount,\n inactiveMemberCount,\n lifetimeMemberEarnings,\n joinPartAgentCount\n ];\n }\n\n function setNewMemberEth(uint val) public onlyOwner {\n if(val == newMemberEth) return;\n newMemberEth = val;\n emit UpdateNewMemberEth(val);\n }\n\n function getEarnings(address member) public view returns (uint256) {\n MemberInfo storage info = memberData[member];\n require(info.status != ActiveStatus.None, \"error_notMember\");\n return\n info.earningsBeforeLastJoin +\n (\n info.status == ActiveStatus.Active\n ? lifetimeMemberEarnings.sub(info.lmeAtJoin)\n : 0\n );\n }\n\n function getWithdrawn(address member) public view returns (uint256) {\n MemberInfo storage info = memberData[member];\n require(info.status != ActiveStatus.None, \"error_notMember\");\n return info.withdrawnEarnings;\n }\n\n function getWithdrawableEarnings(address member) public view returns (uint256) {\n return getEarnings(member).sub(getWithdrawn(member));\n }\n\n function totalWithdrawable() public view returns (uint256) {\n return totalEarnings.sub(totalEarningsWithdrawn);\n }\n\n function addJoinPartAgents(address[] memory agents) public onlyOwner {\n for (uint256 i = 0; i < agents.length; i++) {\n addJoinPartAgent(agents[i]);\n }\n }\n\n function addJoinPartAgent(address agent) public onlyOwner {\n require(joinPartAgents[agent] != ActiveStatus.Active, \"error_alreadyActiveAgent\");\n joinPartAgents[agent] = ActiveStatus.Active;\n emit JoinPartAgentAdded(agent);\n joinPartAgentCount = joinPartAgentCount.add(1);\n }\n\n function removeJoinPartAgent(address agent) public onlyOwner {\n require(joinPartAgents[agent] == ActiveStatus.Active, \"error_notActiveAgent\");\n joinPartAgents[agent] = ActiveStatus.Inactive;\n emit JoinPartAgentRemoved(agent);\n joinPartAgentCount = joinPartAgentCount.sub(1);\n }\n\n /**\n * Process unaccounted tokens that have been sent previously\n * Called by AMB (see DataUnionMainnet:sendTokensToBridge)\n */\n function refreshRevenue() public returns (uint256) {\n uint256 balance = token.balanceOf(address(this));\n uint256 revenue = balance.sub(totalWithdrawable()); // a.sub(b) errors if b > a\n if (revenue == 0 || activeMemberCount == 0) return 0;\n uint256 earningsPerMember = revenue.div(activeMemberCount);\n lifetimeMemberEarnings = lifetimeMemberEarnings.add(earningsPerMember);\n totalEarnings = totalEarnings.add(revenue);\n emit RevenueReceived(revenue);\n emit NewEarnings(earningsPerMember, activeMemberCount);\n return revenue;\n }\n\n function addMember(address payable member) public onlyJoinPartAgent {\n MemberInfo storage info = memberData[member];\n require(info.status != ActiveStatus.Active, \"error_alreadyMember\");\n if(info.status == ActiveStatus.Inactive){\n inactiveMemberCount = inactiveMemberCount.sub(1);\n }\n bool sendEth = info.status == ActiveStatus.None && newMemberEth != 0 && address(this).balance >= newMemberEth;\n info.status = ActiveStatus.Active;\n info.lmeAtJoin = lifetimeMemberEarnings;\n activeMemberCount = activeMemberCount.add(1);\n emit MemberJoined(member);\n\n // give new members ETH. continue even if transfer fails\n if (sendEth) {\n if (member.send(newMemberEth)) {\n NewMemberEthSent(newMemberEth);\n }\n }\n }\n\n function partMember(address member) public {\n require(msg.sender == member || joinPartAgents[msg.sender] == ActiveStatus.Active, \"error_notPermitted\");\n MemberInfo storage info = memberData[member];\n require(info.status == ActiveStatus.Active, \"error_notActiveMember\");\n info.earningsBeforeLastJoin = getEarnings(member);\n info.status = ActiveStatus.Inactive;\n activeMemberCount = activeMemberCount.sub(1);\n inactiveMemberCount = inactiveMemberCount.add(1);\n emit MemberParted(member);\n }\n\n function addMembers(address payable[] memory members) public onlyJoinPartAgent {\n for (uint256 i = 0; i < members.length; i++) {\n addMember(members[i]);\n }\n }\n\n //access checked in partMember\n function partMembers(address[] memory members) public {\n for (uint256 i = 0; i < members.length; i++) {\n partMember(members[i]);\n }\n }\n\n /**\n * Transfer tokens from outside contract, add to a recipient's in-contract balance\n */\n function transferToMemberInContract(address recipient, uint amount) public {\n uint bal_before = token.balanceOf(address(this));\n require(token.transferFrom(msg.sender, address(this), amount), \"error_transfer\");\n uint bal_after = token.balanceOf(address(this));\n require(bal_after.sub(bal_before) >= amount, \"error_transfer\");\n\n _increaseBalance(recipient, amount);\n totalEarnings = totalEarnings.add(amount);\n emit TransferToAddressInContract(msg.sender, recipient, amount);\n }\n\n /**\n * Transfer tokens from sender's in-contract balance to recipient's in-contract balance\n * This is done by \"withdrawing\" sender's earnings and crediting them to recipient's unwithdrawn earnings,\n * so withdrawnEarnings never decreases for anyone (within this function)\n * @param recipient whose withdrawable earnings will increase\n * @param amount how much withdrawable earnings is transferred\n */\n function transferWithinContract(address recipient, uint amount) public {\n require(getWithdrawableEarnings(msg.sender) >= amount, \"error_insufficientBalance\"); // reverts with \"error_notMember\" msg.sender not member\n MemberInfo storage info = memberData[msg.sender];\n info.withdrawnEarnings = info.withdrawnEarnings.add(amount);\n _increaseBalance(recipient, amount);\n emit TransferWithinContract(msg.sender, recipient, amount);\n }\n\n /**\n * Hack to add to single member's balance without affecting lmeAtJoin\n */\n function _increaseBalance(address member, uint amount) internal {\n MemberInfo storage info = memberData[member];\n info.earningsBeforeLastJoin = info.earningsBeforeLastJoin.add(amount);\n\n // allow seeing and withdrawing earnings\n if (info.status == ActiveStatus.None) {\n info.status = ActiveStatus.Inactive;\n inactiveMemberCount = inactiveMemberCount.add(1);\n }\n }\n\n function withdrawMembers(address[] memory members, bool sendToMainnet)\n public\n returns (uint256)\n {\n uint256 withdrawn = 0;\n for (uint256 i = 0; i < members.length; i++) {\n withdrawn = withdrawn.add(withdrawAll(members[i], sendToMainnet));\n }\n return withdrawn;\n }\n\n function withdrawAll(address member, bool sendToMainnet)\n public\n returns (uint256)\n {\n return withdraw(member, getWithdrawableEarnings(member), sendToMainnet);\n }\n\n function withdraw(address member, uint amount, bool sendToMainnet)\n public\n returns (uint256)\n {\n require(msg.sender == member || msg.sender == owner, \"error_notPermitted\");\n return _withdraw(member, member, amount, sendToMainnet);\n }\n\n function withdrawAllTo(address to, bool sendToMainnet)\n public\n returns (uint256)\n {\n return withdrawTo(to, getWithdrawableEarnings(msg.sender), sendToMainnet);\n }\n\n function withdrawTo(address to, uint amount, bool sendToMainnet)\n public\n returns (uint256)\n {\n return _withdraw(msg.sender, to, amount, sendToMainnet);\n }\n\n /**\n * Check signature from a member authorizing withdrawing its earnings to another account.\n * Throws if the signature is badly formatted or doesn't match the given signer and amount.\n * Signature has parts the act as replay protection:\n * 1) `address(this)`: signature can't be used for other contracts;\n * 2) `withdrawn[signer]`: signature only works once (for unspecified amount), and can be \"cancelled\" by sending a withdraw tx.\n * Generated in Javascript with: `web3.eth.accounts.sign(recipientAddress + amount.toString(16, 64) + contractAddress.slice(2) + withdrawnTokens.toString(16, 64), signerPrivateKey)`,\n * or for unlimited amount: `web3.eth.accounts.sign(recipientAddress + \"0\".repeat(64) + contractAddress.slice(2) + withdrawnTokens.toString(16, 64), signerPrivateKey)`.\n * @param signer whose earnings are being withdrawn\n * @param recipient of the tokens\n * @param amount how much is authorized for withdraw, or zero for unlimited (withdrawAll)\n * @param signature byte array from `web3.eth.accounts.sign`\n * @return isValid true iff signer of the authorization (member whose earnings are going to be withdrawn) matches the signature\n */\n function signatureIsValid(\n address signer,\n address recipient,\n uint amount,\n bytes memory signature\n )\n public view\n returns (bool isValid)\n {\n require(signature.length == 65, \"error_badSignatureLength\");\n\n bytes32 r; bytes32 s; uint8 v;\n assembly { // solium-disable-line security/no-inline-assembly\n r := mload(add(signature, 32))\n s := mload(add(signature, 64))\n v := byte(0, mload(add(signature, 96)))\n }\n if (v < 27) {\n v += 27;\n }\n require(v == 27 || v == 28, \"error_badSignatureVersion\");\n\n // When changing the message, remember to double-check that message length is correct!\n bytes32 messageHash = keccak256(abi.encodePacked(\n \"\\x19Ethereum Signed Message:\\n104\", recipient, amount, address(this), getWithdrawn(signer)));\n address calculatedSigner = ecrecover(messageHash, v, r, s);\n\n return calculatedSigner == signer;\n }\n\n /**\n * Do an \"unlimited donate withdraw\" on behalf of someone else, to an address they've specified.\n * Sponsored withdraw is paid by admin, but target account could be whatever the member specifies.\n * The signature gives a \"blank cheque\" for admin to withdraw all tokens to `recipient` in the future,\n * and it's valid until next withdraw (and so can be nullified by withdrawing any amount).\n * A new signature needs to be obtained for each subsequent future withdraw.\n * @param fromSigner whose earnings are being withdrawn\n * @param to the address the tokens will be sent to (instead of `msg.sender`)\n * @param sendToMainnet if the tokens should be sent to mainnet or only withdrawn into sidechain address\n * @param signature from the member, see `signatureIsValid` how signature generated for unlimited amount\n */\n function withdrawAllToSigned(\n address fromSigner,\n address to,\n bool sendToMainnet,\n bytes memory signature\n )\n public\n returns (uint withdrawn)\n {\n require(signatureIsValid(fromSigner, to, 0, signature), \"error_badSignature\");\n return _withdraw(fromSigner, to, getWithdrawableEarnings(fromSigner), sendToMainnet);\n }\n\n /**\n * Do a \"donate withdraw\" on behalf of someone else, to an address they've specified.\n * Sponsored withdraw is paid by admin, but target account could be whatever the member specifies.\n * The signature is valid only for given amount of tokens that may be different from maximum withdrawable tokens.\n * @param fromSigner whose earnings are being withdrawn\n * @param to the address the tokens will be sent to (instead of `msg.sender`)\n * @param amount of tokens to withdraw\n * @param sendToMainnet if the tokens should be sent to mainnet or only withdrawn into sidechain address\n * @param signature from the member, see `signatureIsValid` how signature generated for unlimited amount\n */\n function withdrawToSigned(\n address fromSigner,\n address to,\n uint amount,\n bool sendToMainnet,\n bytes memory signature\n )\n public\n returns (uint withdrawn)\n {\n require(signatureIsValid(fromSigner, to, amount, signature), \"error_badSignature\");\n return _withdraw(fromSigner, to, amount, sendToMainnet);\n }\n\n function toBytes(address a) public pure returns (bytes memory b) {\n assembly {\n let m := mload(0x40)\n a := and(a, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF)\n mstore(\n add(m, 20),\n xor(0x140000000000000000000000000000000000000000, a)\n )\n mstore(0x40, add(m, 52))\n b := m\n }\n }\n\n /**\n * Internal function common to all withdraw methods.\n * Does NOT check proper access, so all callers must do that first.\n */\n function _withdraw(address from, address to, uint amount, bool sendToMainnet)\n internal\n returns (uint256)\n {\n if (amount == 0) return 0;\n require(amount <= getWithdrawableEarnings(from), \"error_insufficientBalance\");\n MemberInfo storage info = memberData[from];\n info.withdrawnEarnings = info.withdrawnEarnings.add(amount);\n totalEarningsWithdrawn = totalEarningsWithdrawn.add(amount);\n if (sendToMainnet)\n require(\n token.transferAndCall(\n tokenMediator,\n amount,\n toBytes(to)\n ),\n \"error_transfer\"\n );\n else require(token.transfer(to, amount), \"error_transfer\");\n emit EarningsWithdrawn(from, amount);\n return amount;\n }\n}\n", + "sourcePath": "/home/heynow/streamr/data-union-solidity/contracts/DataUnionSidechain.sol", + "ast": { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/DataUnionSidechain.sol", + "exportedSymbols": { + "DataUnionSidechain": [ + 2432 + ] + }, + "id": 2433, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 1167, + "literals": [ + "solidity", + "0.6", + ".6" + ], + "nodeType": "PragmaDirective", + "src": "0:22:4" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "file": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "id": 1168, + "nodeType": "ImportDirective", + "scope": 2433, + "sourceUnit": 3642, + "src": "24:59:4", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol", + "file": "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol", + "id": 1169, + "nodeType": "ImportDirective", + "scope": 2433, + "sourceUnit": 4283, + "src": "84:64:4", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/IERC677.sol", + "file": "./IERC677.sol", + "id": 1170, + "nodeType": "ImportDirective", + "scope": 2433, + "sourceUnit": 2940, + "src": "149:23:4", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/Ownable.sol", + "file": "./Ownable.sol", + "id": 1171, + "nodeType": "ImportDirective", + "scope": 2433, + "sourceUnit": 3181, + "src": "173:23:4", + "symbolAliases": [], + "unitAlias": "" + }, + { + "abstract": false, + "baseContracts": [ + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 1172, + "name": "Ownable", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 3180, + "src": "302:7:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_Ownable_$3180", + "typeString": "contract Ownable" + } + }, + "id": 1173, + "nodeType": "InheritanceSpecifier", + "src": "302:7:4" + } + ], + "contractDependencies": [ + 3180 + ], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 2432, + "linearizedBaseContracts": [ + 2432, + 3180 + ], + "name": "DataUnionSidechain", + "nodeType": "ContractDefinition", + "nodes": [ + { + "id": 1176, + "libraryName": { + "contractScope": null, + "id": 1174, + "name": "SafeMath", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 3641, + "src": "322:8:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_SafeMath_$3641", + "typeString": "library SafeMath" + } + }, + "nodeType": "UsingForDirective", + "src": "316:27:4", + "typeName": { + "id": 1175, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "335:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + }, + { + "canonicalName": "DataUnionSidechain.ActiveStatus", + "id": 1180, + "members": [ + { + "id": 1177, + "name": "None", + "nodeType": "EnumValue", + "src": "420:4:4" + }, + { + "id": 1178, + "name": "Active", + "nodeType": "EnumValue", + "src": "426:6:4" + }, + { + "id": 1179, + "name": "Inactive", + "nodeType": "EnumValue", + "src": "434:8:4" + } + ], + "name": "ActiveStatus", + "nodeType": "EnumDefinition", + "src": "401:42:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1184, + "name": "MemberJoined", + "nodeType": "EventDefinition", + "parameters": { + "id": 1183, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1182, + "indexed": true, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1184, + "src": "497:22:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1181, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "497:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "496:24:4" + }, + "src": "478:43:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1188, + "name": "MemberParted", + "nodeType": "EventDefinition", + "parameters": { + "id": 1187, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1186, + "indexed": true, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1188, + "src": "545:22:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1185, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "545:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "544:24:4" + }, + "src": "526:43:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1192, + "name": "JoinPartAgentAdded", + "nodeType": "EventDefinition", + "parameters": { + "id": 1191, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1190, + "indexed": true, + "mutability": "mutable", + "name": "agent", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1192, + "src": "599:21:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1189, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "599:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "598:23:4" + }, + "src": "574:48:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1196, + "name": "JoinPartAgentRemoved", + "nodeType": "EventDefinition", + "parameters": { + "id": 1195, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1194, + "indexed": true, + "mutability": "mutable", + "name": "agent", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1196, + "src": "654:21:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1193, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "654:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "653:23:4" + }, + "src": "627:50:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1200, + "name": "RevenueReceived", + "nodeType": "EventDefinition", + "parameters": { + "id": 1199, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1198, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1200, + "src": "741:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1197, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "741:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "740:16:4" + }, + "src": "719:38:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1206, + "name": "NewEarnings", + "nodeType": "EventDefinition", + "parameters": { + "id": 1205, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1202, + "indexed": false, + "mutability": "mutable", + "name": "earningsPerMember", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1206, + "src": "780:25:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1201, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "780:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1204, + "indexed": false, + "mutability": "mutable", + "name": "activeMemberCount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1206, + "src": "807:25:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1203, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "807:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "779:54:4" + }, + "src": "762:72:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1212, + "name": "EarningsWithdrawn", + "nodeType": "EventDefinition", + "parameters": { + "id": 1211, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1208, + "indexed": true, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1212, + "src": "892:22:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1207, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "892:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1210, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1212, + "src": "916:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1209, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "916:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "891:40:4" + }, + "src": "868:64:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1220, + "name": "TransferWithinContract", + "nodeType": "EventDefinition", + "parameters": { + "id": 1219, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1214, + "indexed": true, + "mutability": "mutable", + "name": "from", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1220, + "src": "995:20:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1213, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "995:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1216, + "indexed": true, + "mutability": "mutable", + "name": "to", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1220, + "src": "1017:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1215, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1017:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1218, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1220, + "src": "1037:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1217, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1037:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "994:55:4" + }, + "src": "966:84:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1228, + "name": "TransferToAddressInContract", + "nodeType": "EventDefinition", + "parameters": { + "id": 1227, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1222, + "indexed": true, + "mutability": "mutable", + "name": "from", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1228, + "src": "1089:20:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1221, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1089:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1224, + "indexed": true, + "mutability": "mutable", + "name": "to", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1228, + "src": "1111:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1223, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1111:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1226, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1228, + "src": "1131:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1225, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1131:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1088:55:4" + }, + "src": "1055:89:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1232, + "name": "UpdateNewMemberEth", + "nodeType": "EventDefinition", + "parameters": { + "id": 1231, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1230, + "indexed": false, + "mutability": "mutable", + "name": "value", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1232, + "src": "1196:10:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1229, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1196:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1195:12:4" + }, + "src": "1171:37:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1236, + "name": "NewMemberEthSent", + "nodeType": "EventDefinition", + "parameters": { + "id": 1235, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1234, + "indexed": false, + "mutability": "mutable", + "name": "amountWei", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1236, + "src": "1236:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1233, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1236:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1235:16:4" + }, + "src": "1213:39:4" + }, + { + "canonicalName": "DataUnionSidechain.MemberInfo", + "id": 1245, + "members": [ + { + "constant": false, + "id": 1238, + "mutability": "mutable", + "name": "status", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1245, + "src": "1286:19:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "typeName": { + "contractScope": null, + "id": 1237, + "name": "ActiveStatus", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1180, + "src": "1286:12:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1240, + "mutability": "mutable", + "name": "earningsBeforeLastJoin", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1245, + "src": "1315:30:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1239, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1315:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1242, + "mutability": "mutable", + "name": "lmeAtJoin", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1245, + "src": "1355:17:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1241, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1355:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1244, + "mutability": "mutable", + "name": "withdrawnEarnings", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1245, + "src": "1382:25:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1243, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1382:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "name": "MemberInfo", + "nodeType": "StructDefinition", + "scope": 2432, + "src": "1258:156:4", + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "fc0c546a", + "id": 1247, + "mutability": "mutable", + "name": "token", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1420:20:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + }, + "typeName": { + "contractScope": null, + "id": 1246, + "name": "IERC677", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 2939, + "src": "1420:7:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "cc772440", + "id": 1249, + "mutability": "mutable", + "name": "tokenMediator", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1446:28:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1248, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1446:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "bf1e42c0", + "id": 1251, + "mutability": "mutable", + "name": "dataUnionMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1480:31:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1250, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1480:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "2df3eba4", + "id": 1253, + "mutability": "mutable", + "name": "totalEarnings", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1518:28:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1252, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1518:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "85a21246", + "id": 1255, + "mutability": "mutable", + "name": "totalEarningsWithdrawn", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1552:37:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1254, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1552:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "5fb6c6ed", + "id": 1257, + "mutability": "mutable", + "name": "activeMemberCount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1596:32:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1256, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1596:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "3d8e36a3", + "id": 1259, + "mutability": "mutable", + "name": "inactiveMemberCount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1634:34:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1258, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1634:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "6d8018b8", + "id": 1261, + "mutability": "mutable", + "name": "lifetimeMemberEarnings", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1674:37:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1260, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1674:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "3ebff90e", + "id": 1263, + "mutability": "mutable", + "name": "joinPartAgentCount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1718:33:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1262, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1718:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "79049017", + "id": 1265, + "mutability": "mutable", + "name": "newMemberEth", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1758:27:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1264, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1758:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "9107d08e", + "id": 1269, + "mutability": "mutable", + "name": "memberData", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1792:48:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo)" + }, + "typeName": { + "id": 1268, + "keyType": { + "id": 1266, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1800:7:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "1792:30:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo)" + }, + "valueType": { + "contractScope": null, + "id": 1267, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "1811:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "c44b73a3", + "id": 1273, + "mutability": "mutable", + "name": "joinPartAgents", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1846:54:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + }, + "typeName": { + "id": 1272, + "keyType": { + "id": 1270, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1854:7:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "1846:32:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + }, + "valueType": { + "contractScope": null, + "id": 1271, + "name": "ActiveStatus", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1180, + "src": "1865:12:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + } + }, + "value": null, + "visibility": "public" + }, + { + "body": { + "id": 1287, + "nodeType": "Block", + "src": "1936:113:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1282, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1276, + "name": "joinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1273, + "src": "1954:14:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1279, + "indexExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1277, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "1969:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1278, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "1969:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1954:26:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1280, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "1984:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1281, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "1984:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "1954:49:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6f6e6c794a6f696e506172744167656e74", + "id": 1283, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2005:25:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_956f78fcce2b1e1b69129df43faa6544de07d17c942f714c7b52da05de24f86e", + "typeString": "literal_string \"error_onlyJoinPartAgent\"" + }, + "value": "error_onlyJoinPartAgent" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_956f78fcce2b1e1b69129df43faa6544de07d17c942f714c7b52da05de24f86e", + "typeString": "literal_string \"error_onlyJoinPartAgent\"" + } + ], + "id": 1275, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "1946:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1284, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1946:85:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1285, + "nodeType": "ExpressionStatement", + "src": "1946:85:4" + }, + { + "id": 1286, + "nodeType": "PlaceholderStatement", + "src": "2041:1:4" + } + ] + }, + "documentation": null, + "id": 1288, + "name": "onlyJoinPartAgent", + "nodeType": "ModifierDefinition", + "overrides": null, + "parameters": { + "id": 1274, + "nodeType": "ParameterList", + "parameters": [], + "src": "1933:2:4" + }, + "src": "1907:142:4", + "virtual": false, + "visibility": "internal" + }, + { + "body": { + "id": 1297, + "nodeType": "Block", + "src": "2137:2:4", + "statements": [] + }, + "documentation": null, + "id": 1298, + "implemented": true, + "kind": "constructor", + "modifiers": [ + { + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "30", + "id": 1293, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2133:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "id": 1292, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2125:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1291, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2125:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1294, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2125:10:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "id": 1295, + "modifierName": { + "argumentTypes": null, + "id": 1290, + "name": "Ownable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3180, + "src": "2117:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_Ownable_$3180_$", + "typeString": "type(contract Ownable)" + } + }, + "nodeType": "ModifierInvocation", + "src": "2117:19:4" + } + ], + "name": "", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1289, + "nodeType": "ParameterList", + "parameters": [], + "src": "2107:2:4" + }, + "returnParameters": { + "id": 1296, + "nodeType": "ParameterList", + "parameters": [], + "src": "2137:0:4" + }, + "scope": 2432, + "src": "2096:43:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1301, + "nodeType": "Block", + "src": "2172:2:4", + "statements": [] + }, + "documentation": null, + "id": 1302, + "implemented": true, + "kind": "receive", + "modifiers": [], + "name": "", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1299, + "nodeType": "ParameterList", + "parameters": [], + "src": "2152:2:4" + }, + "returnParameters": { + "id": 1300, + "nodeType": "ParameterList", + "parameters": [], + "src": "2172:0:4" + }, + "scope": 2432, + "src": "2145:29:4", + "stateMutability": "payable", + "virtual": false, + "visibility": "external" + }, + { + "body": { + "id": 1356, + "nodeType": "Block", + "src": "2437:456:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1321, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "!", + "prefix": true, + "src": "2455:16:4", + "subExpression": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1319, + "name": "isInitialized", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1373, + "src": "2456:13:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_bool_$", + "typeString": "function () view returns (bool)" + } + }, + "id": 1320, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2456:15:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f616c7265616479496e697469616c697a6564", + "id": 1322, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2473:26:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_845b975974cd10e32a9d060d8158a7b328a11a448ea3c75e0603113d3b173fd5", + "typeString": "literal_string \"error_alreadyInitialized\"" + }, + "value": "error_alreadyInitialized" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_845b975974cd10e32a9d060d8158a7b328a11a448ea3c75e0603113d3b173fd5", + "typeString": "literal_string \"error_alreadyInitialized\"" + } + ], + "id": 1318, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "2447:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1323, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2447:53:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1324, + "nodeType": "ExpressionStatement", + "src": "2447:53:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1328, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1325, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "2510:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1326, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "2518:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1327, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "2518:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "src": "2510:18:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 1329, + "nodeType": "ExpressionStatement", + "src": "2510:18:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1334, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1330, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "2633:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1332, + "name": "tokenAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1306, + "src": "2649:12:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1331, + "name": "IERC677", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2939, + "src": "2641:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_IERC677_$2939_$", + "typeString": "type(contract IERC677)" + } + }, + "id": 1333, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2641:21:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "src": "2633:29:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "id": 1335, + "nodeType": "ExpressionStatement", + "src": "2633:29:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1337, + "name": "initialJoinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1309, + "src": "2690:21:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + ], + "id": 1336, + "name": "addJoinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1533, + "src": "2672:17:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_array$_t_address_$dyn_memory_ptr_$returns$__$", + "typeString": "function (address[] memory)" + } + }, + "id": 1338, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2672:40:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1339, + "nodeType": "ExpressionStatement", + "src": "2672:40:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1342, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1340, + "name": "tokenMediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1249, + "src": "2722:13:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 1341, + "name": "tokenMediatorAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1311, + "src": "2738:20:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "2722:36:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 1343, + "nodeType": "ExpressionStatement", + "src": "2722:36:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1346, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1344, + "name": "dataUnionMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1251, + "src": "2768:16:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 1345, + "name": "mainnetDataUnionAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1313, + "src": "2787:23:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "2768:42:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 1347, + "nodeType": "ExpressionStatement", + "src": "2768:42:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1349, + "name": "defaultNewMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1315, + "src": "2836:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1348, + "name": "setNewMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1411, + "src": "2820:15:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 1350, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2820:36:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1351, + "nodeType": "ExpressionStatement", + "src": "2820:36:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1354, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1352, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "2866:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 1353, + "name": "initialOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1304, + "src": "2874:12:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "2866:20:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 1355, + "nodeType": "ExpressionStatement", + "src": "2866:20:4" + } + ] + }, + "documentation": null, + "functionSelector": "015c7f51", + "id": 1357, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "initialize", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1316, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1304, + "mutability": "mutable", + "name": "initialOwner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1357, + "src": "2209:20:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1303, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2209:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1306, + "mutability": "mutable", + "name": "tokenAddress", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1357, + "src": "2239:20:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1305, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2239:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1309, + "mutability": "mutable", + "name": "initialJoinPartAgents", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1357, + "src": "2269:38:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 1307, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2269:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 1308, + "length": null, + "nodeType": "ArrayTypeName", + "src": "2269:9:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1311, + "mutability": "mutable", + "name": "tokenMediatorAddress", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1357, + "src": "2317:28:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1310, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2317:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1313, + "mutability": "mutable", + "name": "mainnetDataUnionAddress", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1357, + "src": "2355:31:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1312, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2355:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1315, + "mutability": "mutable", + "name": "defaultNewMemberEth", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1357, + "src": "2396:27:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1314, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "2396:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2199:230:4" + }, + "returnParameters": { + "id": 1317, + "nodeType": "ParameterList", + "parameters": [], + "src": "2437:0:4" + }, + "scope": 2432, + "src": "2180:713:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1372, + "nodeType": "Block", + "src": "2950:52:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 1370, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1364, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "2975:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + ], + "id": 1363, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2967:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1362, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2967:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1365, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2967:14:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "30", + "id": 1368, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2993:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "id": 1367, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2985:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1366, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2985:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1369, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2985:10:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "src": "2967:28:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "functionReturnParameters": 1361, + "id": 1371, + "nodeType": "Return", + "src": "2960:35:4" + } + ] + }, + "documentation": null, + "functionSelector": "392e53cd", + "id": 1373, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "isInitialized", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1358, + "nodeType": "ParameterList", + "parameters": [], + "src": "2921:2:4" + }, + "returnParameters": { + "id": 1361, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1360, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1373, + "src": "2945:4:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 1359, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "2945:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2944:6:4" + }, + "scope": 2432, + "src": "2899:103:4", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1389, + "nodeType": "Block", + "src": "3235:229:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "components": [ + { + "argumentTypes": null, + "id": 1381, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1253, + "src": "3266:13:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 1382, + "name": "totalEarningsWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1255, + "src": "3293:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 1383, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "3329:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 1384, + "name": "inactiveMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1259, + "src": "3360:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 1385, + "name": "lifetimeMemberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1261, + "src": "3393:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 1386, + "name": "joinPartAgentCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1263, + "src": "3429:18:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "id": 1387, + "isConstant": false, + "isInlineArray": true, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "3252:205:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint256_$6_memory_ptr", + "typeString": "uint256[6] memory" + } + }, + "functionReturnParameters": 1380, + "id": 1388, + "nodeType": "Return", + "src": "3245:212:4" + } + ] + }, + "documentation": { + "id": 1374, + "nodeType": "StructuredDocumentation", + "src": "3008:162:4", + "text": "Atomic getter to get all state variables in one call\nThis alleviates the fact that JSON RPC batch requests aren't available in ethers.js" + }, + "functionSelector": "c59d4847", + "id": 1390, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getStats", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1375, + "nodeType": "ParameterList", + "parameters": [], + "src": "3192:2:4" + }, + "returnParameters": { + "id": 1380, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1379, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1390, + "src": "3216:17:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint256_$6_memory_ptr", + "typeString": "uint256[6]" + }, + "typeName": { + "baseType": { + "id": 1376, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "3216:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1378, + "length": { + "argumentTypes": null, + "hexValue": "36", + "id": 1377, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3224:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_6_by_1", + "typeString": "int_const 6" + }, + "value": "6" + }, + "nodeType": "ArrayTypeName", + "src": "3216:10:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint256_$6_storage_ptr", + "typeString": "uint256[6]" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "3215:19:4" + }, + "scope": 2432, + "src": "3175:289:4", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1410, + "nodeType": "Block", + "src": "3522:113:4", + "statements": [ + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1399, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1397, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1392, + "src": "3535:3:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 1398, + "name": "newMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1265, + "src": "3542:12:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "3535:19:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 1401, + "nodeType": "IfStatement", + "src": "3532:31:4", + "trueBody": { + "expression": null, + "functionReturnParameters": 1396, + "id": 1400, + "nodeType": "Return", + "src": "3556:7:4" + } + }, + { + "expression": { + "argumentTypes": null, + "id": 1404, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1402, + "name": "newMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1265, + "src": "3572:12:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 1403, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1392, + "src": "3587:3:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "3572:18:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1405, + "nodeType": "ExpressionStatement", + "src": "3572:18:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1407, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1392, + "src": "3624:3:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1406, + "name": "UpdateNewMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1232, + "src": "3605:18:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 1408, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3605:23:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1409, + "nodeType": "EmitStatement", + "src": "3600:28:4" + } + ] + }, + "documentation": null, + "functionSelector": "e6018c31", + "id": 1411, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 1395, + "modifierName": { + "argumentTypes": null, + "id": 1394, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "3512:9:4", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "3512:9:4" + } + ], + "name": "setNewMemberEth", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1393, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1392, + "mutability": "mutable", + "name": "val", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1411, + "src": "3495:8:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1391, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "3495:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "3494:10:4" + }, + "returnParameters": { + "id": 1396, + "nodeType": "ParameterList", + "parameters": [], + "src": "3522:0:4" + }, + "scope": 2432, + "src": "3470:165:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1450, + "nodeType": "Block", + "src": "3708:357:4", + "statements": [ + { + "assignments": [ + 1419 + ], + "declarations": [ + { + "constant": false, + "id": 1419, + "mutability": "mutable", + "name": "info", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1450, + "src": "3718:23:4", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + }, + "typeName": { + "contractScope": null, + "id": 1418, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "3718:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1423, + "initialValue": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1420, + "name": "memberData", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1269, + "src": "3744:10:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo storage ref)" + } + }, + "id": 1422, + "indexExpression": { + "argumentTypes": null, + "id": 1421, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1413, + "src": "3755:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "3744:18:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage", + "typeString": "struct DataUnionSidechain.MemberInfo storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "3718:44:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1429, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1425, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1419, + "src": "3780:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1426, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "3780:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1427, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "3795:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1428, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "None", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "3795:17:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "3780:32:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6e6f744d656d626572", + "id": 1430, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3814:17:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_d7a3e0a08d346fd90b7cdd70bcb3d747f808a83a9bae19592a90bf66cd6b4372", + "typeString": "literal_string \"error_notMember\"" + }, + "value": "error_notMember" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_d7a3e0a08d346fd90b7cdd70bcb3d747f808a83a9bae19592a90bf66cd6b4372", + "typeString": "literal_string \"error_notMember\"" + } + ], + "id": 1424, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "3772:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1431, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3772:60:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1432, + "nodeType": "ExpressionStatement", + "src": "3772:60:4" + }, + { + "expression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1448, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1433, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1419, + "src": "3861:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1434, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "earningsBeforeLastJoin", + "nodeType": "MemberAccess", + "referencedDeclaration": 1240, + "src": "3861:27:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "+", + "rightExpression": { + "argumentTypes": null, + "components": [ + { + "argumentTypes": null, + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1439, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1435, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1419, + "src": "3921:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1436, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "3921:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1437, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "3936:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1438, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "3936:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "3921:34:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1445, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4043:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "id": 1446, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "Conditional", + "src": "3921:123:4", + "trueExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1442, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1419, + "src": "4005:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1443, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "lmeAtJoin", + "nodeType": "MemberAccess", + "referencedDeclaration": 1242, + "src": "4005:14:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1440, + "name": "lifetimeMemberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1261, + "src": "3978:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1441, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "3978:26:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1444, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3978:42:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "id": 1447, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "3903:155:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "3861:197:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 1417, + "id": 1449, + "nodeType": "Return", + "src": "3842:216:4" + } + ] + }, + "documentation": null, + "functionSelector": "131b9c04", + "id": 1451, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getEarnings", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1414, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1413, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1451, + "src": "3662:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1412, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3662:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "3661:16:4" + }, + "returnParameters": { + "id": 1417, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1416, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1451, + "src": "3699:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1415, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "3699:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "3698:9:4" + }, + "scope": 2432, + "src": "3641:424:4", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1476, + "nodeType": "Block", + "src": "4139:170:4", + "statements": [ + { + "assignments": [ + 1459 + ], + "declarations": [ + { + "constant": false, + "id": 1459, + "mutability": "mutable", + "name": "info", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1476, + "src": "4149:23:4", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + }, + "typeName": { + "contractScope": null, + "id": 1458, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "4149:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1463, + "initialValue": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1460, + "name": "memberData", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1269, + "src": "4175:10:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo storage ref)" + } + }, + "id": 1462, + "indexExpression": { + "argumentTypes": null, + "id": 1461, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1453, + "src": "4186:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "4175:18:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage", + "typeString": "struct DataUnionSidechain.MemberInfo storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "4149:44:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1469, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1465, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1459, + "src": "4211:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1466, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "4211:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1467, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "4226:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1468, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "None", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "4226:17:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "4211:32:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6e6f744d656d626572", + "id": 1470, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4245:17:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_d7a3e0a08d346fd90b7cdd70bcb3d747f808a83a9bae19592a90bf66cd6b4372", + "typeString": "literal_string \"error_notMember\"" + }, + "value": "error_notMember" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_d7a3e0a08d346fd90b7cdd70bcb3d747f808a83a9bae19592a90bf66cd6b4372", + "typeString": "literal_string \"error_notMember\"" + } + ], + "id": 1464, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "4203:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1471, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4203:60:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1472, + "nodeType": "ExpressionStatement", + "src": "4203:60:4" + }, + { + "expression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1473, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1459, + "src": "4280:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1474, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "withdrawnEarnings", + "nodeType": "MemberAccess", + "referencedDeclaration": 1244, + "src": "4280:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 1457, + "id": 1475, + "nodeType": "Return", + "src": "4273:29:4" + } + ] + }, + "documentation": null, + "functionSelector": "ae66d948", + "id": 1477, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getWithdrawn", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1454, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1453, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1477, + "src": "4093:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1452, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4093:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4092:16:4" + }, + "returnParameters": { + "id": 1457, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1456, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1477, + "src": "4130:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1455, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4130:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4129:9:4" + }, + "scope": 2432, + "src": "4071:238:4", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1493, + "nodeType": "Block", + "src": "4394:69:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1489, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1479, + "src": "4448:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1488, + "name": "getWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1477, + "src": "4435:12:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 1490, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4435:20:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1485, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1479, + "src": "4423:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1484, + "name": "getEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1451, + "src": "4411:11:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 1486, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4411:19:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1487, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "4411:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1491, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4411:45:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 1483, + "id": 1492, + "nodeType": "Return", + "src": "4404:52:4" + } + ] + }, + "documentation": null, + "functionSelector": "2e0d4212", + "id": 1494, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getWithdrawableEarnings", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1480, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1479, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1494, + "src": "4348:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1478, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4348:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4347:16:4" + }, + "returnParameters": { + "id": 1483, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1482, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1494, + "src": "4385:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1481, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4385:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4384:9:4" + }, + "scope": 2432, + "src": "4315:148:4", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1504, + "nodeType": "Block", + "src": "4528:65:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1501, + "name": "totalEarningsWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1255, + "src": "4563:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1499, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1253, + "src": "4545:13:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1500, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "4545:17:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1502, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4545:41:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 1498, + "id": 1503, + "nodeType": "Return", + "src": "4538:48:4" + } + ] + }, + "documentation": null, + "functionSelector": "0600a865", + "id": 1505, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "totalWithdrawable", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1495, + "nodeType": "ParameterList", + "parameters": [], + "src": "4495:2:4" + }, + "returnParameters": { + "id": 1498, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1497, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1505, + "src": "4519:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1496, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4519:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4518:9:4" + }, + "scope": 2432, + "src": "4469:124:4", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1532, + "nodeType": "Block", + "src": "4668:112:4", + "statements": [ + { + "body": { + "id": 1530, + "nodeType": "Block", + "src": "4722:52:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1525, + "name": "agents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1508, + "src": "4753:6:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + }, + "id": 1527, + "indexExpression": { + "argumentTypes": null, + "id": 1526, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1514, + "src": "4760:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "4753:9:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1524, + "name": "addJoinPartAgent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1569, + "src": "4736:16:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 1528, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4736:27:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1529, + "nodeType": "ExpressionStatement", + "src": "4736:27:4" + } + ] + }, + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1520, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1517, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1514, + "src": "4698:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "<", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1518, + "name": "agents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1508, + "src": "4702:6:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + }, + "id": 1519, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "length", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "4702:13:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "4698:17:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 1531, + "initializationExpression": { + "assignments": [ + 1514 + ], + "declarations": [ + { + "constant": false, + "id": 1514, + "mutability": "mutable", + "name": "i", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1531, + "src": "4683:9:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1513, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4683:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1516, + "initialValue": { + "argumentTypes": null, + "hexValue": "30", + "id": 1515, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4695:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "nodeType": "VariableDeclarationStatement", + "src": "4683:13:4" + }, + "loopExpression": { + "expression": { + "argumentTypes": null, + "id": 1522, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "++", + "prefix": false, + "src": "4717:3:4", + "subExpression": { + "argumentTypes": null, + "id": 1521, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1514, + "src": "4717:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1523, + "nodeType": "ExpressionStatement", + "src": "4717:3:4" + }, + "nodeType": "ForStatement", + "src": "4678:96:4" + } + ] + }, + "documentation": null, + "functionSelector": "1796621a", + "id": 1533, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 1511, + "modifierName": { + "argumentTypes": null, + "id": 1510, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "4658:9:4", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "4658:9:4" + } + ], + "name": "addJoinPartAgents", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1509, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1508, + "mutability": "mutable", + "name": "agents", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1533, + "src": "4626:23:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 1506, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4626:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 1507, + "length": null, + "nodeType": "ArrayTypeName", + "src": "4626:9:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4625:25:4" + }, + "returnParameters": { + "id": 1512, + "nodeType": "ParameterList", + "parameters": [], + "src": "4668:0:4" + }, + "scope": 2432, + "src": "4599:181:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1568, + "nodeType": "Block", + "src": "4844:247:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1546, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1541, + "name": "joinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1273, + "src": "4862:14:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1543, + "indexExpression": { + "argumentTypes": null, + "id": 1542, + "name": "agent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1535, + "src": "4877:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "4862:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1544, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "4887:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1545, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "4887:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "4862:44:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f616c72656164794163746976654167656e74", + "id": 1547, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4908:26:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_bb39e853702abde1810741d4774a63438edaeee888b9b2a936f664fafe2a45b8", + "typeString": "literal_string \"error_alreadyActiveAgent\"" + }, + "value": "error_alreadyActiveAgent" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_bb39e853702abde1810741d4774a63438edaeee888b9b2a936f664fafe2a45b8", + "typeString": "literal_string \"error_alreadyActiveAgent\"" + } + ], + "id": 1540, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "4854:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1548, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4854:81:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1549, + "nodeType": "ExpressionStatement", + "src": "4854:81:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1555, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1550, + "name": "joinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1273, + "src": "4945:14:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1552, + "indexExpression": { + "argumentTypes": null, + "id": 1551, + "name": "agent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1535, + "src": "4960:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "4945:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1553, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "4969:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1554, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "4969:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "4945:43:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "id": 1556, + "nodeType": "ExpressionStatement", + "src": "4945:43:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1558, + "name": "agent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1535, + "src": "5022:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1557, + "name": "JoinPartAgentAdded", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1192, + "src": "5003:18:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 1559, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5003:25:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1560, + "nodeType": "EmitStatement", + "src": "4998:30:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1566, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1561, + "name": "joinPartAgentCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1263, + "src": "5038:18:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "31", + "id": 1564, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5082:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "argumentTypes": null, + "id": 1562, + "name": "joinPartAgentCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1263, + "src": "5059:18:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1563, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "5059:22:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1565, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5059:25:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "5038:46:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1567, + "nodeType": "ExpressionStatement", + "src": "5038:46:4" + } + ] + }, + "documentation": null, + "functionSelector": "662d45a2", + "id": 1569, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 1538, + "modifierName": { + "argumentTypes": null, + "id": 1537, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "4834:9:4", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "4834:9:4" + } + ], + "name": "addJoinPartAgent", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1536, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1535, + "mutability": "mutable", + "name": "agent", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1569, + "src": "4812:13:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1534, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4812:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4811:15:4" + }, + "returnParameters": { + "id": 1539, + "nodeType": "ParameterList", + "parameters": [], + "src": "4844:0:4" + }, + "scope": 2432, + "src": "4786:305:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1604, + "nodeType": "Block", + "src": "5158:247:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1582, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1577, + "name": "joinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1273, + "src": "5176:14:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1579, + "indexExpression": { + "argumentTypes": null, + "id": 1578, + "name": "agent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1571, + "src": "5191:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "5176:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1580, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "5201:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1581, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "5201:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "5176:44:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6e6f744163746976654167656e74", + "id": 1583, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5222:22:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_7374b1c602a903c4ee0ecf07cf7d2bb5da07e6616a0fd70b600a11d82b670e51", + "typeString": "literal_string \"error_notActiveAgent\"" + }, + "value": "error_notActiveAgent" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_7374b1c602a903c4ee0ecf07cf7d2bb5da07e6616a0fd70b600a11d82b670e51", + "typeString": "literal_string \"error_notActiveAgent\"" + } + ], + "id": 1576, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "5168:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1584, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5168:77:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1585, + "nodeType": "ExpressionStatement", + "src": "5168:77:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1591, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1586, + "name": "joinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1273, + "src": "5255:14:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1588, + "indexExpression": { + "argumentTypes": null, + "id": 1587, + "name": "agent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1571, + "src": "5270:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "5255:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1589, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "5279:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1590, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Inactive", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "5279:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "5255:45:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "id": 1592, + "nodeType": "ExpressionStatement", + "src": "5255:45:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1594, + "name": "agent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1571, + "src": "5336:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1593, + "name": "JoinPartAgentRemoved", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1196, + "src": "5315:20:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 1595, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5315:27:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1596, + "nodeType": "EmitStatement", + "src": "5310:32:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1602, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1597, + "name": "joinPartAgentCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1263, + "src": "5352:18:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "31", + "id": 1600, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5396:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "argumentTypes": null, + "id": 1598, + "name": "joinPartAgentCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1263, + "src": "5373:18:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1599, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "5373:22:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1601, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5373:25:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "5352:46:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1603, + "nodeType": "ExpressionStatement", + "src": "5352:46:4" + } + ] + }, + "documentation": null, + "functionSelector": "09a6400b", + "id": 1605, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 1574, + "modifierName": { + "argumentTypes": null, + "id": 1573, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "5148:9:4", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "5148:9:4" + } + ], + "name": "removeJoinPartAgent", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1572, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1571, + "mutability": "mutable", + "name": "agent", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1605, + "src": "5126:13:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1570, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "5126:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "5125:15:4" + }, + "returnParameters": { + "id": 1575, + "nodeType": "ParameterList", + "parameters": [], + "src": "5158:0:4" + }, + "scope": 2432, + "src": "5097:308:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1671, + "nodeType": "Block", + "src": "5606:542:4", + "statements": [ + { + "assignments": [ + 1612 + ], + "declarations": [ + { + "constant": false, + "id": 1612, + "mutability": "mutable", + "name": "balance", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1671, + "src": "5616:15:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1611, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "5616:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1620, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1617, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "5658:4:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + ], + "id": 1616, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "5650:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1615, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "5650:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1618, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5650:13:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "expression": { + "argumentTypes": null, + "id": 1613, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "5634:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "id": 1614, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "balanceOf", + "nodeType": "MemberAccess", + "referencedDeclaration": 4221, + "src": "5634:15:4", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view external returns (uint256)" + } + }, + "id": 1619, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5634:30:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "5616:48:4" + }, + { + "assignments": [ + 1622 + ], + "declarations": [ + { + "constant": false, + "id": 1622, + "mutability": "mutable", + "name": "revenue", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1671, + "src": "5674:15:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1621, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "5674:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1628, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1625, + "name": "totalWithdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1505, + "src": "5704:17:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", + "typeString": "function () view returns (uint256)" + } + }, + "id": 1626, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5704:19:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1623, + "name": "balance", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1612, + "src": "5692:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1624, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "5692:11:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1627, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5692:32:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "5674:50:4" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 1635, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1631, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1629, + "name": "revenue", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1622, + "src": "5766:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1630, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5777:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "5766:12:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "||", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1634, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1632, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "5782:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1633, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5803:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "5782:22:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "5766:38:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 1638, + "nodeType": "IfStatement", + "src": "5762:52:4", + "trueBody": { + "expression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1636, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5813:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "functionReturnParameters": 1610, + "id": 1637, + "nodeType": "Return", + "src": "5806:8:4" + } + }, + { + "assignments": [ + 1640 + ], + "declarations": [ + { + "constant": false, + "id": 1640, + "mutability": "mutable", + "name": "earningsPerMember", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1671, + "src": "5824:25:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1639, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "5824:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1645, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1643, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "5864:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1641, + "name": "revenue", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1622, + "src": "5852:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1642, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "div", + "nodeType": "MemberAccess", + "referencedDeclaration": 3571, + "src": "5852:11:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1644, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5852:30:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "5824:58:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1651, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1646, + "name": "lifetimeMemberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1261, + "src": "5892:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1649, + "name": "earningsPerMember", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1640, + "src": "5944:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1647, + "name": "lifetimeMemberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1261, + "src": "5917:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1648, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "5917:26:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1650, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5917:45:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "5892:70:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1652, + "nodeType": "ExpressionStatement", + "src": "5892:70:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1658, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1653, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1253, + "src": "5972:13:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1656, + "name": "revenue", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1622, + "src": "6006:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1654, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1253, + "src": "5988:13:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1655, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "5988:17:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1657, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5988:26:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "5972:42:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1659, + "nodeType": "ExpressionStatement", + "src": "5972:42:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1661, + "name": "revenue", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1622, + "src": "6045:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1660, + "name": "RevenueReceived", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1200, + "src": "6029:15:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 1662, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6029:24:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1663, + "nodeType": "EmitStatement", + "src": "6024:29:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1665, + "name": "earningsPerMember", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1640, + "src": "6080:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 1666, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "6099:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1664, + "name": "NewEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1206, + "src": "6068:11:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$_t_uint256_$returns$__$", + "typeString": "function (uint256,uint256)" + } + }, + "id": 1667, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6068:49:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1668, + "nodeType": "EmitStatement", + "src": "6063:54:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1669, + "name": "revenue", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1622, + "src": "6134:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 1610, + "id": 1670, + "nodeType": "Return", + "src": "6127:14:4" + } + ] + }, + "documentation": { + "id": 1606, + "nodeType": "StructuredDocumentation", + "src": "5411:139:4", + "text": "Process unaccounted tokens that have been sent previously\nCalled by AMB (see DataUnionMainnet:sendTokensToBridge)" + }, + "functionSelector": "331beb5f", + "id": 1672, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "refreshRevenue", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1607, + "nodeType": "ParameterList", + "parameters": [], + "src": "5578:2:4" + }, + "returnParameters": { + "id": 1610, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1609, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1672, + "src": "5597:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1608, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "5597:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "5596:9:4" + }, + "scope": 2432, + "src": "5555:593:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1765, + "nodeType": "Block", + "src": "6222:765:4", + "statements": [ + { + "assignments": [ + 1680 + ], + "declarations": [ + { + "constant": false, + "id": 1680, + "mutability": "mutable", + "name": "info", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1765, + "src": "6232:23:4", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + }, + "typeName": { + "contractScope": null, + "id": 1679, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "6232:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1684, + "initialValue": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1681, + "name": "memberData", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1269, + "src": "6258:10:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo storage ref)" + } + }, + "id": 1683, + "indexExpression": { + "argumentTypes": null, + "id": 1682, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1674, + "src": "6269:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "6258:18:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage", + "typeString": "struct DataUnionSidechain.MemberInfo storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "6232:44:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1690, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1686, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1680, + "src": "6294:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1687, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "6294:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1688, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "6309:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1689, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "6309:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "6294:34:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f616c72656164794d656d626572", + "id": 1691, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "6330:21:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_0cd731b7251f7a1d03a588db9c28b82c775f5ea903082a5ec49eb1fc1ac0eb1f", + "typeString": "literal_string \"error_alreadyMember\"" + }, + "value": "error_alreadyMember" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_0cd731b7251f7a1d03a588db9c28b82c775f5ea903082a5ec49eb1fc1ac0eb1f", + "typeString": "literal_string \"error_alreadyMember\"" + } + ], + "id": 1685, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "6286:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1692, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6286:66:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1693, + "nodeType": "ExpressionStatement", + "src": "6286:66:4" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1698, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1694, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1680, + "src": "6365:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1695, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "6365:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1696, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "6380:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1697, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Inactive", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "6380:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "6365:36:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 1707, + "nodeType": "IfStatement", + "src": "6362:113:4", + "trueBody": { + "id": 1706, + "nodeType": "Block", + "src": "6402:73:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 1704, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1699, + "name": "inactiveMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1259, + "src": "6416:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "31", + "id": 1702, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "6462:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "argumentTypes": null, + "id": 1700, + "name": "inactiveMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1259, + "src": "6438:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1701, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "6438:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1703, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6438:26:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "6416:48:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1705, + "nodeType": "ExpressionStatement", + "src": "6416:48:4" + } + ] + } + }, + { + "assignments": [ + 1709 + ], + "declarations": [ + { + "constant": false, + "id": 1709, + "mutability": "mutable", + "name": "sendEth", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1765, + "src": "6484:12:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 1708, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "6484:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1727, + "initialValue": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 1726, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 1718, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1714, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1710, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1680, + "src": "6499:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1711, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "6499:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1712, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "6514:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1713, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "None", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "6514:17:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "6499:32:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "&&", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1717, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1715, + "name": "newMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1265, + "src": "6535:12:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1716, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "6551:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "6535:17:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "6499:53:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "&&", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1725, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1721, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "6564:4:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + ], + "id": 1720, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "6556:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1719, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "6556:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1722, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6556:13:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "id": 1723, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "balance", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "6556:21:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">=", + "rightExpression": { + "argumentTypes": null, + "id": 1724, + "name": "newMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1265, + "src": "6581:12:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "6556:37:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "6499:94:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "6484:109:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1733, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1728, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1680, + "src": "6603:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1730, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "6603:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1731, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "6617:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1732, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "6617:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "6603:33:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "id": 1734, + "nodeType": "ExpressionStatement", + "src": "6603:33:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1739, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1735, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1680, + "src": "6646:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1737, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "lmeAtJoin", + "nodeType": "MemberAccess", + "referencedDeclaration": 1242, + "src": "6646:14:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 1738, + "name": "lifetimeMemberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1261, + "src": "6663:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "6646:39:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1740, + "nodeType": "ExpressionStatement", + "src": "6646:39:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1746, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1741, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "6695:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "31", + "id": 1744, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "6737:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "argumentTypes": null, + "id": 1742, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "6715:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1743, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "6715:21:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1745, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6715:24:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "6695:44:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1747, + "nodeType": "ExpressionStatement", + "src": "6695:44:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1749, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1674, + "src": "6767:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "id": 1748, + "name": "MemberJoined", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1184, + "src": "6754:12:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 1750, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6754:20:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1751, + "nodeType": "EmitStatement", + "src": "6749:25:4" + }, + { + "condition": { + "argumentTypes": null, + "id": 1752, + "name": "sendEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1709, + "src": "6854:7:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 1764, + "nodeType": "IfStatement", + "src": "6850:131:4", + "trueBody": { + "id": 1763, + "nodeType": "Block", + "src": "6863:118:4", + "statements": [ + { + "condition": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1755, + "name": "newMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1265, + "src": "6893:12:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1753, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1674, + "src": "6881:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "id": 1754, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "send", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "6881:11:4", + "typeDescriptions": { + "typeIdentifier": "t_function_send_nonpayable$_t_uint256_$returns$_t_bool_$", + "typeString": "function (uint256) returns (bool)" + } + }, + "id": 1756, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6881:25:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 1762, + "nodeType": "IfStatement", + "src": "6877:94:4", + "trueBody": { + "id": 1761, + "nodeType": "Block", + "src": "6908:63:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1758, + "name": "newMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1265, + "src": "6943:12:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1757, + "name": "NewMemberEthSent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1236, + "src": "6926:16:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 1759, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6926:30:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1760, + "nodeType": "ExpressionStatement", + "src": "6926:30:4" + } + ] + } + } + ] + } + } + ] + }, + "documentation": null, + "functionSelector": "ca6d56dc", + "id": 1766, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 1677, + "modifierName": { + "argumentTypes": null, + "id": 1676, + "name": "onlyJoinPartAgent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1288, + "src": "6204:17:4", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "6204:17:4" + } + ], + "name": "addMember", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1675, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1674, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1766, + "src": "6173:22:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + "typeName": { + "id": 1673, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "6173:15:4", + "stateMutability": "payable", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "6172:24:4" + }, + "returnParameters": { + "id": 1678, + "nodeType": "ParameterList", + "parameters": [], + "src": "6222:0:4" + }, + "scope": 2432, + "src": "6154:833:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1835, + "nodeType": "Block", + "src": "7036:504:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 1783, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 1775, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1772, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "7054:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1773, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "7054:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 1774, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1768, + "src": "7068:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "7054:20:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "||", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1782, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1776, + "name": "joinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1273, + "src": "7078:14:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1779, + "indexExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1777, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "7093:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1778, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "7093:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "7078:26:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1780, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "7108:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1781, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "7108:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "7078:49:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "7054:73:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6e6f745065726d6974746564", + "id": 1784, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7129:20:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_81fcea6533090d67b5d2d1910c87832a3a952f4bfc10eedbdaba5b028593bde1", + "typeString": "literal_string \"error_notPermitted\"" + }, + "value": "error_notPermitted" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_81fcea6533090d67b5d2d1910c87832a3a952f4bfc10eedbdaba5b028593bde1", + "typeString": "literal_string \"error_notPermitted\"" + } + ], + "id": 1771, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "7046:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1785, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7046:104:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1786, + "nodeType": "ExpressionStatement", + "src": "7046:104:4" + }, + { + "assignments": [ + 1788 + ], + "declarations": [ + { + "constant": false, + "id": 1788, + "mutability": "mutable", + "name": "info", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1835, + "src": "7160:23:4", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + }, + "typeName": { + "contractScope": null, + "id": 1787, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "7160:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1792, + "initialValue": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1789, + "name": "memberData", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1269, + "src": "7186:10:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo storage ref)" + } + }, + "id": 1791, + "indexExpression": { + "argumentTypes": null, + "id": 1790, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1768, + "src": "7197:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "7186:18:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage", + "typeString": "struct DataUnionSidechain.MemberInfo storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "7160:44:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1798, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1794, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1788, + "src": "7222:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1795, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "7222:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1796, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "7237:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1797, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "7237:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "7222:34:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6e6f744163746976654d656d626572", + "id": 1799, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7258:23:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_bc4828de8fccb67618bdc2462d405754421b1e879aff6628f11ddb63528355b9", + "typeString": "literal_string \"error_notActiveMember\"" + }, + "value": "error_notActiveMember" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_bc4828de8fccb67618bdc2462d405754421b1e879aff6628f11ddb63528355b9", + "typeString": "literal_string \"error_notActiveMember\"" + } + ], + "id": 1793, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "7214:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1800, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7214:68:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1801, + "nodeType": "ExpressionStatement", + "src": "7214:68:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1808, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1802, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1788, + "src": "7292:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1804, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "earningsBeforeLastJoin", + "nodeType": "MemberAccess", + "referencedDeclaration": 1240, + "src": "7292:27:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1806, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1768, + "src": "7334:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1805, + "name": "getEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1451, + "src": "7322:11:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 1807, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7322:19:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "7292:49:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1809, + "nodeType": "ExpressionStatement", + "src": "7292:49:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1815, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1810, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1788, + "src": "7351:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1812, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "7351:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1813, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "7365:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1814, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Inactive", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "7365:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "7351:35:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "id": 1816, + "nodeType": "ExpressionStatement", + "src": "7351:35:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1822, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1817, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "7396:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "31", + "id": 1820, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7438:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "argumentTypes": null, + "id": 1818, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "7416:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1819, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "7416:21:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1821, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7416:24:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "7396:44:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1823, + "nodeType": "ExpressionStatement", + "src": "7396:44:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1829, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1824, + "name": "inactiveMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1259, + "src": "7450:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "31", + "id": 1827, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7496:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "argumentTypes": null, + "id": 1825, + "name": "inactiveMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1259, + "src": "7472:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1826, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "7472:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1828, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7472:26:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "7450:48:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1830, + "nodeType": "ExpressionStatement", + "src": "7450:48:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1832, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1768, + "src": "7526:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1831, + "name": "MemberParted", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1188, + "src": "7513:12:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 1833, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7513:20:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1834, + "nodeType": "EmitStatement", + "src": "7508:25:4" + } + ] + }, + "documentation": null, + "functionSelector": "4e40ea64", + "id": 1836, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "partMember", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1769, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1768, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1836, + "src": "7013:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1767, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "7013:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "7012:16:4" + }, + "returnParameters": { + "id": 1770, + "nodeType": "ParameterList", + "parameters": [], + "src": "7036:0:4" + }, + "scope": 2432, + "src": "6993:547:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1863, + "nodeType": "Block", + "src": "7625:107:4", + "statements": [ + { + "body": { + "id": 1861, + "nodeType": "Block", + "src": "7680:46:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1856, + "name": "members", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1839, + "src": "7704:7:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_payable_$dyn_memory_ptr", + "typeString": "address payable[] memory" + } + }, + "id": 1858, + "indexExpression": { + "argumentTypes": null, + "id": 1857, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1845, + "src": "7712:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "7704:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "id": 1855, + "name": "addMember", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1766, + "src": "7694:9:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_payable_$returns$__$", + "typeString": "function (address payable)" + } + }, + "id": 1859, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7694:21:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1860, + "nodeType": "ExpressionStatement", + "src": "7694:21:4" + } + ] + }, + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1851, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1848, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1845, + "src": "7655:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "<", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1849, + "name": "members", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1839, + "src": "7659:7:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_payable_$dyn_memory_ptr", + "typeString": "address payable[] memory" + } + }, + "id": 1850, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "length", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "7659:14:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "7655:18:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 1862, + "initializationExpression": { + "assignments": [ + 1845 + ], + "declarations": [ + { + "constant": false, + "id": 1845, + "mutability": "mutable", + "name": "i", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1862, + "src": "7640:9:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1844, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "7640:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1847, + "initialValue": { + "argumentTypes": null, + "hexValue": "30", + "id": 1846, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7652:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "nodeType": "VariableDeclarationStatement", + "src": "7640:13:4" + }, + "loopExpression": { + "expression": { + "argumentTypes": null, + "id": 1853, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "++", + "prefix": false, + "src": "7675:3:4", + "subExpression": { + "argumentTypes": null, + "id": 1852, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1845, + "src": "7675:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1854, + "nodeType": "ExpressionStatement", + "src": "7675:3:4" + }, + "nodeType": "ForStatement", + "src": "7635:91:4" + } + ] + }, + "documentation": null, + "functionSelector": "6f4d469b", + "id": 1864, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 1842, + "modifierName": { + "argumentTypes": null, + "id": 1841, + "name": "onlyJoinPartAgent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1288, + "src": "7607:17:4", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "7607:17:4" + } + ], + "name": "addMembers", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1840, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1839, + "mutability": "mutable", + "name": "members", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1864, + "src": "7566:32:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_payable_$dyn_memory_ptr", + "typeString": "address payable[]" + }, + "typeName": { + "baseType": { + "id": 1837, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "7566:15:4", + "stateMutability": "payable", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "id": 1838, + "length": null, + "nodeType": "ArrayTypeName", + "src": "7566:17:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_payable_$dyn_storage_ptr", + "typeString": "address payable[]" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "7565:34:4" + }, + "returnParameters": { + "id": 1843, + "nodeType": "ParameterList", + "parameters": [], + "src": "7625:0:4" + }, + "scope": 2432, + "src": "7546:186:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1889, + "nodeType": "Block", + "src": "7827:108:4", + "statements": [ + { + "body": { + "id": 1887, + "nodeType": "Block", + "src": "7882:47:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1882, + "name": "members", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1867, + "src": "7907:7:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + }, + "id": 1884, + "indexExpression": { + "argumentTypes": null, + "id": 1883, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1871, + "src": "7915:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "7907:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1881, + "name": "partMember", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1836, + "src": "7896:10:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 1885, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7896:22:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1886, + "nodeType": "ExpressionStatement", + "src": "7896:22:4" + } + ] + }, + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1877, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1874, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1871, + "src": "7857:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "<", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1875, + "name": "members", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1867, + "src": "7861:7:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + }, + "id": 1876, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "length", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "7861:14:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "7857:18:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 1888, + "initializationExpression": { + "assignments": [ + 1871 + ], + "declarations": [ + { + "constant": false, + "id": 1871, + "mutability": "mutable", + "name": "i", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1888, + "src": "7842:9:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1870, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "7842:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1873, + "initialValue": { + "argumentTypes": null, + "hexValue": "30", + "id": 1872, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7854:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "nodeType": "VariableDeclarationStatement", + "src": "7842:13:4" + }, + "loopExpression": { + "expression": { + "argumentTypes": null, + "id": 1879, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "++", + "prefix": false, + "src": "7877:3:4", + "subExpression": { + "argumentTypes": null, + "id": 1878, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1871, + "src": "7877:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1880, + "nodeType": "ExpressionStatement", + "src": "7877:3:4" + }, + "nodeType": "ForStatement", + "src": "7837:92:4" + } + ] + }, + "documentation": null, + "functionSelector": "7b30ed43", + "id": 1890, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "partMembers", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1868, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1867, + "mutability": "mutable", + "name": "members", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1890, + "src": "7794:24:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 1865, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "7794:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 1866, + "length": null, + "nodeType": "ArrayTypeName", + "src": "7794:9:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "7793:26:4" + }, + "returnParameters": { + "id": 1869, + "nodeType": "ParameterList", + "parameters": [], + "src": "7827:0:4" + }, + "scope": 2432, + "src": "7773:162:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1961, + "nodeType": "Block", + "src": "8119:455:4", + "statements": [ + { + "assignments": [ + 1899 + ], + "declarations": [ + { + "constant": false, + "id": 1899, + "mutability": "mutable", + "name": "bal_before", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1961, + "src": "8129:15:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1898, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "8129:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1907, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1904, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "8171:4:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + ], + "id": 1903, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "8163:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1902, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "8163:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1905, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8163:13:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "expression": { + "argumentTypes": null, + "id": 1900, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "8147:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "id": 1901, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "balanceOf", + "nodeType": "MemberAccess", + "referencedDeclaration": 4221, + "src": "8147:15:4", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view external returns (uint256)" + } + }, + "id": 1906, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8147:30:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "8129:48:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1911, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "8214:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1912, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "8214:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1915, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "8234:4:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + ], + "id": 1914, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "8226:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1913, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "8226:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1916, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8226:13:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 1917, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1895, + "src": "8241:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1909, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "8195:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "id": 1910, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "transferFrom", + "nodeType": "MemberAccess", + "referencedDeclaration": 4263, + "src": "8195:18:4", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$_t_bool_$", + "typeString": "function (address,address,uint256) external returns (bool)" + } + }, + "id": 1918, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8195:53:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f7472616e73666572", + "id": 1919, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "8250:16:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + }, + "value": "error_transfer" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + } + ], + "id": 1908, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "8187:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1920, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8187:80:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1921, + "nodeType": "ExpressionStatement", + "src": "8187:80:4" + }, + { + "assignments": [ + 1923 + ], + "declarations": [ + { + "constant": false, + "id": 1923, + "mutability": "mutable", + "name": "bal_after", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1961, + "src": "8277:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1922, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "8277:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1931, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1928, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "8318:4:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + ], + "id": 1927, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "8310:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1926, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "8310:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1929, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8310:13:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "expression": { + "argumentTypes": null, + "id": 1924, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "8294:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "id": 1925, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "balanceOf", + "nodeType": "MemberAccess", + "referencedDeclaration": 4221, + "src": "8294:15:4", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view external returns (uint256)" + } + }, + "id": 1930, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8294:30:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "8277:47:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1938, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1935, + "name": "bal_before", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1899, + "src": "8356:10:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1933, + "name": "bal_after", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1923, + "src": "8342:9:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1934, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "8342:13:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1936, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8342:25:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">=", + "rightExpression": { + "argumentTypes": null, + "id": 1937, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1895, + "src": "8371:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "8342:35:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f7472616e73666572", + "id": 1939, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "8379:16:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + }, + "value": "error_transfer" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + } + ], + "id": 1932, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "8334:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1940, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8334:62:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1941, + "nodeType": "ExpressionStatement", + "src": "8334:62:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1943, + "name": "recipient", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1893, + "src": "8424:9:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1944, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1895, + "src": "8435:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1942, + "name": "_increaseBalance", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2056, + "src": "8407:16:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 1945, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8407:35:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1946, + "nodeType": "ExpressionStatement", + "src": "8407:35:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1952, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1947, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1253, + "src": "8452:13:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1950, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1895, + "src": "8486:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1948, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1253, + "src": "8468:13:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1949, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "8468:17:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1951, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8468:25:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "8452:41:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1953, + "nodeType": "ExpressionStatement", + "src": "8452:41:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1955, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "8536:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1956, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "8536:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 1957, + "name": "recipient", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1893, + "src": "8548:9:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1958, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1895, + "src": "8560:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1954, + "name": "TransferToAddressInContract", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1228, + "src": "8508:27:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,address,uint256)" + } + }, + "id": 1959, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8508:59:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1960, + "nodeType": "EmitStatement", + "src": "8503:64:4" + } + ] + }, + "documentation": { + "id": 1891, + "nodeType": "StructuredDocumentation", + "src": "7941:98:4", + "text": "Transfer tokens from outside contract, add to a recipient's in-contract balance" + }, + "functionSelector": "b274bcc7", + "id": 1962, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "transferToMemberInContract", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1896, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1893, + "mutability": "mutable", + "name": "recipient", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1962, + "src": "8080:17:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1892, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "8080:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1895, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1962, + "src": "8099:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1894, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "8099:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "8079:32:4" + }, + "returnParameters": { + "id": 1897, + "nodeType": "ParameterList", + "parameters": [], + "src": "8119:0:4" + }, + "scope": 2432, + "src": "8044:530:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2009, + "nodeType": "Block", + "src": "9083:399:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1976, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1972, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "9125:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1973, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "9125:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "id": 1971, + "name": "getWithdrawableEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1494, + "src": "9101:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 1974, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "9101:35:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">=", + "rightExpression": { + "argumentTypes": null, + "id": 1975, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1967, + "src": "9140:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "9101:45:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f696e73756666696369656e7442616c616e6365", + "id": 1977, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9148:27:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_74799d65376d943bac023262731d6421d2e6b6332748a55385eb85a0683327fc", + "typeString": "literal_string \"error_insufficientBalance\"" + }, + "value": "error_insufficientBalance" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_74799d65376d943bac023262731d6421d2e6b6332748a55385eb85a0683327fc", + "typeString": "literal_string \"error_insufficientBalance\"" + } + ], + "id": 1970, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "9093:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1978, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "9093:83:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1979, + "nodeType": "ExpressionStatement", + "src": "9093:83:4" + }, + { + "assignments": [ + 1981 + ], + "declarations": [ + { + "constant": false, + "id": 1981, + "mutability": "mutable", + "name": "info", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2009, + "src": "9245:23:4", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + }, + "typeName": { + "contractScope": null, + "id": 1980, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "9245:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1986, + "initialValue": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1982, + "name": "memberData", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1269, + "src": "9271:10:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo storage ref)" + } + }, + "id": 1985, + "indexExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1983, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "9282:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1984, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "9282:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "9271:22:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage", + "typeString": "struct DataUnionSidechain.MemberInfo storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "9245:48:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1995, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1987, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1981, + "src": "9303:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1989, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "withdrawnEarnings", + "nodeType": "MemberAccess", + "referencedDeclaration": 1244, + "src": "9303:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1993, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1967, + "src": "9355:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1990, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1981, + "src": "9328:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1991, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "withdrawnEarnings", + "nodeType": "MemberAccess", + "referencedDeclaration": 1244, + "src": "9328:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1992, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "9328:26:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1994, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "9328:34:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "9303:59:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1996, + "nodeType": "ExpressionStatement", + "src": "9303:59:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1998, + "name": "recipient", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1965, + "src": "9389:9:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1999, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1967, + "src": "9400:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1997, + "name": "_increaseBalance", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2056, + "src": "9372:16:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 2000, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "9372:35:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2001, + "nodeType": "ExpressionStatement", + "src": "9372:35:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2003, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "9445:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 2004, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "9445:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 2005, + "name": "recipient", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1965, + "src": "9457:9:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2006, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1967, + "src": "9468:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 2002, + "name": "TransferWithinContract", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1220, + "src": "9422:22:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,address,uint256)" + } + }, + "id": 2007, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "9422:53:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2008, + "nodeType": "EmitStatement", + "src": "9417:58:4" + } + ] + }, + "documentation": { + "id": 1963, + "nodeType": "StructuredDocumentation", + "src": "8580:427:4", + "text": "Transfer tokens from sender's in-contract balance to recipient's in-contract balance\nThis is done by \"withdrawing\" sender's earnings and crediting them to recipient's unwithdrawn earnings,\n so withdrawnEarnings never decreases for anyone (within this function)\n@param recipient whose withdrawable earnings will increase\n@param amount how much withdrawable earnings is transferred" + }, + "functionSelector": "71cdfd68", + "id": 2010, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "transferWithinContract", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1968, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1965, + "mutability": "mutable", + "name": "recipient", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2010, + "src": "9044:17:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1964, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "9044:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1967, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2010, + "src": "9063:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1966, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "9063:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "9043:32:4" + }, + "returnParameters": { + "id": 1969, + "nodeType": "ParameterList", + "parameters": [], + "src": "9083:0:4" + }, + "scope": 2432, + "src": "9012:470:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2055, + "nodeType": "Block", + "src": "9642:359:4", + "statements": [ + { + "assignments": [ + 2019 + ], + "declarations": [ + { + "constant": false, + "id": 2019, + "mutability": "mutable", + "name": "info", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2055, + "src": "9652:23:4", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + }, + "typeName": { + "contractScope": null, + "id": 2018, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "9652:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2023, + "initialValue": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 2020, + "name": "memberData", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1269, + "src": "9678:10:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo storage ref)" + } + }, + "id": 2022, + "indexExpression": { + "argumentTypes": null, + "id": 2021, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2013, + "src": "9689:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "9678:18:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage", + "typeString": "struct DataUnionSidechain.MemberInfo storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "9652:44:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 2032, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2024, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2019, + "src": "9706:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 2026, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "earningsBeforeLastJoin", + "nodeType": "MemberAccess", + "referencedDeclaration": 1240, + "src": "9706:27:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2030, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2015, + "src": "9768:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2027, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2019, + "src": "9736:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 2028, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "earningsBeforeLastJoin", + "nodeType": "MemberAccess", + "referencedDeclaration": 1240, + "src": "9736:27:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2029, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "9736:31:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 2031, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "9736:39:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "9706:69:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2033, + "nodeType": "ExpressionStatement", + "src": "9706:69:4" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 2038, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2034, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2019, + "src": "9839:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 2035, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "9839:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2036, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "9854:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 2037, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "None", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "9854:17:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "9839:32:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 2054, + "nodeType": "IfStatement", + "src": "9835:160:4", + "trueBody": { + "id": 2053, + "nodeType": "Block", + "src": "9873:122:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 2044, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2039, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2019, + "src": "9887:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 2041, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "9887:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2042, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "9901:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 2043, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Inactive", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "9901:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "9887:35:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "id": 2045, + "nodeType": "ExpressionStatement", + "src": "9887:35:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 2051, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 2046, + "name": "inactiveMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1259, + "src": "9936:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "31", + "id": 2049, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9982:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "argumentTypes": null, + "id": 2047, + "name": "inactiveMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1259, + "src": "9958:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2048, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "9958:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 2050, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "9958:26:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "9936:48:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2052, + "nodeType": "ExpressionStatement", + "src": "9936:48:4" + } + ] + } + } + ] + }, + "documentation": { + "id": 2011, + "nodeType": "StructuredDocumentation", + "src": "9488:85:4", + "text": "Hack to add to single member's balance without affecting lmeAtJoin" + }, + "id": 2056, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "_increaseBalance", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2016, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2013, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2056, + "src": "9604:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2012, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "9604:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2015, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2056, + "src": "9620:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2014, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "9620:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "9603:29:4" + }, + "returnParameters": { + "id": 2017, + "nodeType": "ParameterList", + "parameters": [], + "src": "9642:0:4" + }, + "scope": 2432, + "src": "9578:423:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "internal" + }, + { + "body": { + "id": 2097, + "nodeType": "Block", + "src": "10123:208:4", + "statements": [ + { + "assignments": [ + 2067 + ], + "declarations": [ + { + "constant": false, + "id": 2067, + "mutability": "mutable", + "name": "withdrawn", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2097, + "src": "10133:17:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2066, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "10133:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2069, + "initialValue": { + "argumentTypes": null, + "hexValue": "30", + "id": 2068, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "10153:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "nodeType": "VariableDeclarationStatement", + "src": "10133:21:4" + }, + { + "body": { + "id": 2093, + "nodeType": "Block", + "src": "10209:90:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 2091, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 2081, + "name": "withdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2067, + "src": "10223:9:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 2085, + "name": "members", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2059, + "src": "10261:7:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + }, + "id": 2087, + "indexExpression": { + "argumentTypes": null, + "id": 2086, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2071, + "src": "10269:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "10261:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2088, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2061, + "src": "10273:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 2084, + "name": "withdrawAll", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2116, + "src": "10249:11:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_bool_$returns$_t_uint256_$", + "typeString": "function (address,bool) returns (uint256)" + } + }, + "id": 2089, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10249:38:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 2082, + "name": "withdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2067, + "src": "10235:9:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2083, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "10235:13:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 2090, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10235:53:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "10223:65:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2092, + "nodeType": "ExpressionStatement", + "src": "10223:65:4" + } + ] + }, + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 2077, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 2074, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2071, + "src": "10184:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "<", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2075, + "name": "members", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2059, + "src": "10188:7:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + }, + "id": 2076, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "length", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "10188:14:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "10184:18:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 2094, + "initializationExpression": { + "assignments": [ + 2071 + ], + "declarations": [ + { + "constant": false, + "id": 2071, + "mutability": "mutable", + "name": "i", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2094, + "src": "10169:9:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2070, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "10169:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2073, + "initialValue": { + "argumentTypes": null, + "hexValue": "30", + "id": 2072, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "10181:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "nodeType": "VariableDeclarationStatement", + "src": "10169:13:4" + }, + "loopExpression": { + "expression": { + "argumentTypes": null, + "id": 2079, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "++", + "prefix": false, + "src": "10204:3:4", + "subExpression": { + "argumentTypes": null, + "id": 2078, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2071, + "src": "10204:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2080, + "nodeType": "ExpressionStatement", + "src": "10204:3:4" + }, + "nodeType": "ForStatement", + "src": "10164:135:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 2095, + "name": "withdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2067, + "src": "10315:9:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2065, + "id": 2096, + "nodeType": "Return", + "src": "10308:16:4" + } + ] + }, + "documentation": null, + "functionSelector": "a4d6ddc0", + "id": 2098, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdrawMembers", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2062, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2059, + "mutability": "mutable", + "name": "members", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2098, + "src": "10032:24:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 2057, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "10032:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 2058, + "length": null, + "nodeType": "ArrayTypeName", + "src": "10032:9:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2061, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2098, + "src": "10058:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2060, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "10058:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10031:46:4" + }, + "returnParameters": { + "id": 2065, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2064, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2098, + "src": "10110:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2063, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "10110:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10109:9:4" + }, + "scope": 2432, + "src": "10007:324:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2115, + "nodeType": "Block", + "src": "10439:88:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2108, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2100, + "src": "10465:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2110, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2100, + "src": "10497:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 2109, + "name": "getWithdrawableEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1494, + "src": "10473:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 2111, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10473:31:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 2112, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2102, + "src": "10506:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 2107, + "name": "withdraw", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2148, + "src": "10456:8:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$_t_bool_$returns$_t_uint256_$", + "typeString": "function (address,uint256,bool) returns (uint256)" + } + }, + "id": 2113, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10456:64:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2106, + "id": 2114, + "nodeType": "Return", + "src": "10449:71:4" + } + ] + }, + "documentation": null, + "functionSelector": "4bee9137", + "id": 2116, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdrawAll", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2103, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2100, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2116, + "src": "10358:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2099, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "10358:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2102, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2116, + "src": "10374:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2101, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "10374:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10357:36:4" + }, + "returnParameters": { + "id": 2106, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2105, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2116, + "src": "10426:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2104, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "10426:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10425:9:4" + }, + "scope": 2432, + "src": "10337:190:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2147, + "nodeType": "Block", + "src": "10645:156:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 2136, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 2131, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2128, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "10663:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 2129, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "10663:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 2130, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2118, + "src": "10677:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "10663:20:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "||", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 2135, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2132, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "10687:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 2133, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "10687:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 2134, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "10701:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "10687:19:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "10663:43:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6e6f745065726d6974746564", + "id": 2137, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "10708:20:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_81fcea6533090d67b5d2d1910c87832a3a952f4bfc10eedbdaba5b028593bde1", + "typeString": "literal_string \"error_notPermitted\"" + }, + "value": "error_notPermitted" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_81fcea6533090d67b5d2d1910c87832a3a952f4bfc10eedbdaba5b028593bde1", + "typeString": "literal_string \"error_notPermitted\"" + } + ], + "id": 2127, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "10655:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2138, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10655:74:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2139, + "nodeType": "ExpressionStatement", + "src": "10655:74:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2141, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2118, + "src": "10756:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2142, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2118, + "src": "10764:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2143, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2120, + "src": "10772:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 2144, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2122, + "src": "10780:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 2140, + "name": "_withdraw", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2431, + "src": "10746:9:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_address_$_t_uint256_$_t_bool_$returns$_t_uint256_$", + "typeString": "function (address,address,uint256,bool) returns (uint256)" + } + }, + "id": 2145, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10746:48:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2126, + "id": 2146, + "nodeType": "Return", + "src": "10739:55:4" + } + ] + }, + "documentation": null, + "functionSelector": "ead5d359", + "id": 2148, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdraw", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2123, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2118, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2148, + "src": "10551:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2117, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "10551:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2120, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2148, + "src": "10567:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2119, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "10567:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2122, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2148, + "src": "10580:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2121, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "10580:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10550:49:4" + }, + "returnParameters": { + "id": 2126, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2125, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2148, + "src": "10632:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2124, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "10632:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10631:9:4" + }, + "scope": 2432, + "src": "10533:268:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2166, + "nodeType": "Block", + "src": "10907:90:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2158, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2150, + "src": "10935:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2160, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "10963:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 2161, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "10963:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "id": 2159, + "name": "getWithdrawableEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1494, + "src": "10939:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 2162, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10939:35:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 2163, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2152, + "src": "10976:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 2157, + "name": "withdrawTo", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2187, + "src": "10924:10:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$_t_bool_$returns$_t_uint256_$", + "typeString": "function (address,uint256,bool) returns (uint256)" + } + }, + "id": 2164, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10924:66:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2156, + "id": 2165, + "nodeType": "Return", + "src": "10917:73:4" + } + ] + }, + "documentation": null, + "functionSelector": "2b94411f", + "id": 2167, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdrawAllTo", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2153, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2150, + "mutability": "mutable", + "name": "to", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2167, + "src": "10830:10:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2149, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "10830:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2152, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2167, + "src": "10842:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2151, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "10842:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10829:32:4" + }, + "returnParameters": { + "id": 2156, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2155, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2167, + "src": "10894:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2154, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "10894:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10893:9:4" + }, + "scope": 2432, + "src": "10807:190:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2186, + "nodeType": "Block", + "src": "11113:72:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2179, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "11140:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 2180, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "11140:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 2181, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2169, + "src": "11152:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2182, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2171, + "src": "11156:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 2183, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2173, + "src": "11164:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 2178, + "name": "_withdraw", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2431, + "src": "11130:9:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_address_$_t_uint256_$_t_bool_$returns$_t_uint256_$", + "typeString": "function (address,address,uint256,bool) returns (uint256)" + } + }, + "id": 2184, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "11130:48:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2177, + "id": 2185, + "nodeType": "Return", + "src": "11123:55:4" + } + ] + }, + "documentation": null, + "functionSelector": "73e2290c", + "id": 2187, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdrawTo", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2174, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2169, + "mutability": "mutable", + "name": "to", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2187, + "src": "11023:10:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2168, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "11023:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2171, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2187, + "src": "11035:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2170, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "11035:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2173, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2187, + "src": "11048:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2172, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "11048:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "11022:45:4" + }, + "returnParameters": { + "id": 2177, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2176, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2187, + "src": "11100:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2175, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "11100:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "11099:9:4" + }, + "scope": 2432, + "src": "11003:182:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2270, + "nodeType": "Block", + "src": "12594:831:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 2205, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2202, + "name": "signature", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2196, + "src": "12612:9:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + "id": 2203, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "length", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "12612:16:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "3635", + "id": 2204, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "12632:2:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_65_by_1", + "typeString": "int_const 65" + }, + "value": "65" + }, + "src": "12612:22:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6261645369676e61747572654c656e677468", + "id": 2206, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "12636:26:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_4077da77d01843e066cb3ce202dcb5c333f11623972b2a584316e75b72c622e0", + "typeString": "literal_string \"error_badSignatureLength\"" + }, + "value": "error_badSignatureLength" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_4077da77d01843e066cb3ce202dcb5c333f11623972b2a584316e75b72c622e0", + "typeString": "literal_string \"error_badSignatureLength\"" + } + ], + "id": 2201, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "12604:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2207, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "12604:59:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2208, + "nodeType": "ExpressionStatement", + "src": "12604:59:4" + }, + { + "assignments": [ + 2210 + ], + "declarations": [ + { + "constant": false, + "id": 2210, + "mutability": "mutable", + "name": "r", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2270, + "src": "12674:9:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 2209, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "12674:7:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2211, + "initialValue": null, + "nodeType": "VariableDeclarationStatement", + "src": "12674:9:4" + }, + { + "assignments": [ + 2213 + ], + "declarations": [ + { + "constant": false, + "id": 2213, + "mutability": "mutable", + "name": "s", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2270, + "src": "12685:9:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 2212, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "12685:7:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2214, + "initialValue": null, + "nodeType": "VariableDeclarationStatement", + "src": "12685:9:4" + }, + { + "assignments": [ + 2216 + ], + "declarations": [ + { + "constant": false, + "id": 2216, + "mutability": "mutable", + "name": "v", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2270, + "src": "12696:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 2215, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "12696:5:4", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2217, + "initialValue": null, + "nodeType": "VariableDeclarationStatement", + "src": "12696:7:4" + }, + { + "AST": { + "nodeType": "YulBlock", + "src": "12722:205:4", + "statements": [ + { + "nodeType": "YulAssignment", + "src": "12792:30:4", + "value": { + "arguments": [ + { + "arguments": [ + { + "name": "signature", + "nodeType": "YulIdentifier", + "src": "12807:9:4" + }, + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "12818:2:4", + "type": "", + "value": "32" + } + ], + "functionName": { + "name": "add", + "nodeType": "YulIdentifier", + "src": "12803:3:4" + }, + "nodeType": "YulFunctionCall", + "src": "12803:18:4" + } + ], + "functionName": { + "name": "mload", + "nodeType": "YulIdentifier", + "src": "12797:5:4" + }, + "nodeType": "YulFunctionCall", + "src": "12797:25:4" + }, + "variableNames": [ + { + "name": "r", + "nodeType": "YulIdentifier", + "src": "12792:1:4" + } + ] + }, + { + "nodeType": "YulAssignment", + "src": "12835:30:4", + "value": { + "arguments": [ + { + "arguments": [ + { + "name": "signature", + "nodeType": "YulIdentifier", + "src": "12850:9:4" + }, + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "12861:2:4", + "type": "", + "value": "64" + } + ], + "functionName": { + "name": "add", + "nodeType": "YulIdentifier", + "src": "12846:3:4" + }, + "nodeType": "YulFunctionCall", + "src": "12846:18:4" + } + ], + "functionName": { + "name": "mload", + "nodeType": "YulIdentifier", + "src": "12840:5:4" + }, + "nodeType": "YulFunctionCall", + "src": "12840:25:4" + }, + "variableNames": [ + { + "name": "s", + "nodeType": "YulIdentifier", + "src": "12835:1:4" + } + ] + }, + { + "nodeType": "YulAssignment", + "src": "12878:39:4", + "value": { + "arguments": [ + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "12888:1:4", + "type": "", + "value": "0" + }, + { + "arguments": [ + { + "arguments": [ + { + "name": "signature", + "nodeType": "YulIdentifier", + "src": "12901:9:4" + }, + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "12912:2:4", + "type": "", + "value": "96" + } + ], + "functionName": { + "name": "add", + "nodeType": "YulIdentifier", + "src": "12897:3:4" + }, + "nodeType": "YulFunctionCall", + "src": "12897:18:4" + } + ], + "functionName": { + "name": "mload", + "nodeType": "YulIdentifier", + "src": "12891:5:4" + }, + "nodeType": "YulFunctionCall", + "src": "12891:25:4" + } + ], + "functionName": { + "name": "byte", + "nodeType": "YulIdentifier", + "src": "12883:4:4" + }, + "nodeType": "YulFunctionCall", + "src": "12883:34:4" + }, + "variableNames": [ + { + "name": "v", + "nodeType": "YulIdentifier", + "src": "12878:1:4" + } + ] + } + ] + }, + "evmVersion": "istanbul", + "externalReferences": [ + { + "declaration": 2210, + "isOffset": false, + "isSlot": false, + "src": "12792:1:4", + "valueSize": 1 + }, + { + "declaration": 2213, + "isOffset": false, + "isSlot": false, + "src": "12835:1:4", + "valueSize": 1 + }, + { + "declaration": 2196, + "isOffset": false, + "isSlot": false, + "src": "12807:9:4", + "valueSize": 1 + }, + { + "declaration": 2196, + "isOffset": false, + "isSlot": false, + "src": "12850:9:4", + "valueSize": 1 + }, + { + "declaration": 2196, + "isOffset": false, + "isSlot": false, + "src": "12901:9:4", + "valueSize": 1 + }, + { + "declaration": 2216, + "isOffset": false, + "isSlot": false, + "src": "12878:1:4", + "valueSize": 1 + } + ], + "id": 2218, + "nodeType": "InlineAssembly", + "src": "12713:214:4" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "id": 2221, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 2219, + "name": "v", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2216, + "src": "12940:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "BinaryOperation", + "operator": "<", + "rightExpression": { + "argumentTypes": null, + "hexValue": "3237", + "id": 2220, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "12944:2:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_27_by_1", + "typeString": "int_const 27" + }, + "value": "27" + }, + "src": "12940:6:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 2227, + "nodeType": "IfStatement", + "src": "12936:44:4", + "trueBody": { + "id": 2226, + "nodeType": "Block", + "src": "12948:32:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 2224, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 2222, + "name": "v", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2216, + "src": "12962:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "Assignment", + "operator": "+=", + "rightHandSide": { + "argumentTypes": null, + "hexValue": "3237", + "id": 2223, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "12967:2:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_27_by_1", + "typeString": "int_const 27" + }, + "value": "27" + }, + "src": "12962:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "id": 2225, + "nodeType": "ExpressionStatement", + "src": "12962:7:4" + } + ] + } + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 2235, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "id": 2231, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 2229, + "name": "v", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2216, + "src": "12997:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "3237", + "id": 2230, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "13002:2:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_27_by_1", + "typeString": "int_const 27" + }, + "value": "27" + }, + "src": "12997:7:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "||", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "id": 2234, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 2232, + "name": "v", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2216, + "src": "13008:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "3238", + "id": 2233, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "13013:2:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_28_by_1", + "typeString": "int_const 28" + }, + "value": "28" + }, + "src": "13008:7:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "12997:18:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6261645369676e617475726556657273696f6e", + "id": 2236, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "13017:27:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_8fa0eae5d18986979b1a6399b1aa7a4d8b3c5537b79b5808266bd780a9c464db", + "typeString": "literal_string \"error_badSignatureVersion\"" + }, + "value": "error_badSignatureVersion" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_8fa0eae5d18986979b1a6399b1aa7a4d8b3c5537b79b5808266bd780a9c464db", + "typeString": "literal_string \"error_badSignatureVersion\"" + } + ], + "id": 2228, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "12989:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2237, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "12989:56:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2238, + "nodeType": "ExpressionStatement", + "src": "12989:56:4" + }, + { + "assignments": [ + 2240 + ], + "declarations": [ + { + "constant": false, + "id": 2240, + "mutability": "mutable", + "name": "messageHash", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2270, + "src": "13151:19:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 2239, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "13151:7:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2256, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "19457468657265756d205369676e6564204d6573736167653a0a313034", + "id": 2244, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "13213:35:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_8d960065d1d57999e7234857e6a0652c524bb2aeb2c4d7c6b962ce1fe99d1ca0", + "typeString": "literal_string \"\u0019Ethereum Signed Message:\n104\"" + }, + "value": "\u0019Ethereum Signed Message:\n104" + }, + { + "argumentTypes": null, + "id": 2245, + "name": "recipient", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2192, + "src": "13250:9:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2246, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2194, + "src": "13261:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2249, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "13277:4:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + ], + "id": 2248, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "13269:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 2247, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "13269:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 2250, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "13269:13:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2252, + "name": "signer", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2190, + "src": "13297:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 2251, + "name": "getWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1477, + "src": "13284:12:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 2253, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "13284:20:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_8d960065d1d57999e7234857e6a0652c524bb2aeb2c4d7c6b962ce1fe99d1ca0", + "typeString": "literal_string \"\u0019Ethereum Signed Message:\n104\"" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 2242, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "13183:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 2243, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encodePacked", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "13183:16:4", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencodepacked_pure$__$returns$_t_bytes_memory_ptr_$", + "typeString": "function () pure returns (bytes memory)" + } + }, + "id": 2254, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "13183:122:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "id": 2241, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "13173:9:4", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 2255, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "13173:133:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "13151:155:4" + }, + { + "assignments": [ + 2258 + ], + "declarations": [ + { + "constant": false, + "id": 2258, + "mutability": "mutable", + "name": "calculatedSigner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2270, + "src": "13316:24:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2257, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "13316:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2265, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2260, + "name": "messageHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2240, + "src": "13353:11:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "argumentTypes": null, + "id": 2261, + "name": "v", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2216, + "src": "13366:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + { + "argumentTypes": null, + "id": 2262, + "name": "r", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2210, + "src": "13369:1:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "argumentTypes": null, + "id": 2263, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2213, + "src": "13372:1:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 2259, + "name": "ecrecover", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -6, + "src": "13343:9:4", + "typeDescriptions": { + "typeIdentifier": "t_function_ecrecover_pure$_t_bytes32_$_t_uint8_$_t_bytes32_$_t_bytes32_$returns$_t_address_$", + "typeString": "function (bytes32,uint8,bytes32,bytes32) pure returns (address)" + } + }, + "id": 2264, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "13343:31:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "13316:58:4" + }, + { + "expression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 2268, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 2266, + "name": "calculatedSigner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2258, + "src": "13392:16:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 2267, + "name": "signer", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2190, + "src": "13412:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "13392:26:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "functionReturnParameters": 2200, + "id": 2269, + "nodeType": "Return", + "src": "13385:33:4" + } + ] + }, + "documentation": { + "id": 2188, + "nodeType": "StructuredDocumentation", + "src": "11191:1207:4", + "text": "Check signature from a member authorizing withdrawing its earnings to another account.\nThrows if the signature is badly formatted or doesn't match the given signer and amount.\nSignature has parts the act as replay protection:\n1) `address(this)`: signature can't be used for other contracts;\n2) `withdrawn[signer]`: signature only works once (for unspecified amount), and can be \"cancelled\" by sending a withdraw tx.\nGenerated in Javascript with: `web3.eth.accounts.sign(recipientAddress + amount.toString(16, 64) + contractAddress.slice(2) + withdrawnTokens.toString(16, 64), signerPrivateKey)`,\nor for unlimited amount: `web3.eth.accounts.sign(recipientAddress + \"0\".repeat(64) + contractAddress.slice(2) + withdrawnTokens.toString(16, 64), signerPrivateKey)`.\n@param signer whose earnings are being withdrawn\n@param recipient of the tokens\n@param amount how much is authorized for withdraw, or zero for unlimited (withdrawAll)\n@param signature byte array from `web3.eth.accounts.sign`\n@return isValid true iff signer of the authorization (member whose earnings are going to be withdrawn) matches the signature" + }, + "functionSelector": "a2d3cf4b", + "id": 2271, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "signatureIsValid", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2197, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2190, + "mutability": "mutable", + "name": "signer", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2271, + "src": "12438:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2189, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "12438:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2192, + "mutability": "mutable", + "name": "recipient", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2271, + "src": "12462:17:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2191, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "12462:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2194, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2271, + "src": "12489:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2193, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "12489:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2196, + "mutability": "mutable", + "name": "signature", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2271, + "src": "12510:22:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 2195, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "12510:5:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "12428:110:4" + }, + "returnParameters": { + "id": 2200, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2199, + "mutability": "mutable", + "name": "isValid", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2271, + "src": "12576:12:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2198, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "12576:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "12575:14:4" + }, + "scope": 2432, + "src": "12403:1022:4", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2304, + "nodeType": "Block", + "src": "14491:188:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2287, + "name": "fromSigner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2274, + "src": "14526:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2288, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2276, + "src": "14538:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "hexValue": "30", + "id": 2289, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "14542:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + { + "argumentTypes": null, + "id": 2290, + "name": "signature", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2280, + "src": "14545:9:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "id": 2286, + "name": "signatureIsValid", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2271, + "src": "14509:16:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$_t_address_$_t_uint256_$_t_bytes_memory_ptr_$returns$_t_bool_$", + "typeString": "function (address,address,uint256,bytes memory) view returns (bool)" + } + }, + "id": 2291, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "14509:46:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6261645369676e6174757265", + "id": 2292, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "14557:20:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_2ec4ea0e2156213061c2a099c127d1a6548cdd3e5b4bf1f695ceebe1aeb9d2b2", + "typeString": "literal_string \"error_badSignature\"" + }, + "value": "error_badSignature" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_2ec4ea0e2156213061c2a099c127d1a6548cdd3e5b4bf1f695ceebe1aeb9d2b2", + "typeString": "literal_string \"error_badSignature\"" + } + ], + "id": 2285, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "14501:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2293, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "14501:77:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2294, + "nodeType": "ExpressionStatement", + "src": "14501:77:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2296, + "name": "fromSigner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2274, + "src": "14605:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2297, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2276, + "src": "14617:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2299, + "name": "fromSigner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2274, + "src": "14645:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 2298, + "name": "getWithdrawableEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1494, + "src": "14621:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 2300, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "14621:35:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 2301, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2278, + "src": "14658:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 2295, + "name": "_withdraw", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2431, + "src": "14595:9:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_address_$_t_uint256_$_t_bool_$returns$_t_uint256_$", + "typeString": "function (address,address,uint256,bool) returns (uint256)" + } + }, + "id": 2302, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "14595:77:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2284, + "id": 2303, + "nodeType": "Return", + "src": "14588:84:4" + } + ] + }, + "documentation": { + "id": 2272, + "nodeType": "StructuredDocumentation", + "src": "13431:860:4", + "text": "Do an \"unlimited donate withdraw\" on behalf of someone else, to an address they've specified.\nSponsored withdraw is paid by admin, but target account could be whatever the member specifies.\nThe signature gives a \"blank cheque\" for admin to withdraw all tokens to `recipient` in the future,\n and it's valid until next withdraw (and so can be nullified by withdrawing any amount).\nA new signature needs to be obtained for each subsequent future withdraw.\n@param fromSigner whose earnings are being withdrawn\n@param to the address the tokens will be sent to (instead of `msg.sender`)\n@param sendToMainnet if the tokens should be sent to mainnet or only withdrawn into sidechain address\n@param signature from the member, see `signatureIsValid` how signature generated for unlimited amount" + }, + "functionSelector": "ce7b7864", + "id": 2305, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdrawAllToSigned", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2281, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2274, + "mutability": "mutable", + "name": "fromSigner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2305, + "src": "14334:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2273, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "14334:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2276, + "mutability": "mutable", + "name": "to", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2305, + "src": "14362:10:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2275, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "14362:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2278, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2305, + "src": "14382:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2277, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "14382:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2280, + "mutability": "mutable", + "name": "signature", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2305, + "src": "14410:22:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 2279, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "14410:5:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "14324:114:4" + }, + "returnParameters": { + "id": 2284, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2283, + "mutability": "mutable", + "name": "withdrawn", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2305, + "src": "14471:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2282, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "14471:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "14470:16:4" + }, + "scope": 2432, + "src": "14296:383:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2338, + "nodeType": "Block", + "src": "15628:164:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2323, + "name": "fromSigner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2308, + "src": "15663:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2324, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2310, + "src": "15675:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2325, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2312, + "src": "15679:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 2326, + "name": "signature", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2316, + "src": "15687:9:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "id": 2322, + "name": "signatureIsValid", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2271, + "src": "15646:16:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$_t_address_$_t_uint256_$_t_bytes_memory_ptr_$returns$_t_bool_$", + "typeString": "function (address,address,uint256,bytes memory) view returns (bool)" + } + }, + "id": 2327, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "15646:51:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6261645369676e6174757265", + "id": 2328, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "15699:20:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_2ec4ea0e2156213061c2a099c127d1a6548cdd3e5b4bf1f695ceebe1aeb9d2b2", + "typeString": "literal_string \"error_badSignature\"" + }, + "value": "error_badSignature" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_2ec4ea0e2156213061c2a099c127d1a6548cdd3e5b4bf1f695ceebe1aeb9d2b2", + "typeString": "literal_string \"error_badSignature\"" + } + ], + "id": 2321, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "15638:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2329, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "15638:82:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2330, + "nodeType": "ExpressionStatement", + "src": "15638:82:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2332, + "name": "fromSigner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2308, + "src": "15747:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2333, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2310, + "src": "15759:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2334, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2312, + "src": "15763:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 2335, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2314, + "src": "15771:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 2331, + "name": "_withdraw", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2431, + "src": "15737:9:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_address_$_t_uint256_$_t_bool_$returns$_t_uint256_$", + "typeString": "function (address,address,uint256,bool) returns (uint256)" + } + }, + "id": 2336, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "15737:48:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2320, + "id": 2337, + "nodeType": "Return", + "src": "15730:55:4" + } + ] + }, + "documentation": { + "id": 2306, + "nodeType": "StructuredDocumentation", + "src": "14685:725:4", + "text": "Do a \"donate withdraw\" on behalf of someone else, to an address they've specified.\nSponsored withdraw is paid by admin, but target account could be whatever the member specifies.\nThe signature is valid only for given amount of tokens that may be different from maximum withdrawable tokens.\n@param fromSigner whose earnings are being withdrawn\n@param to the address the tokens will be sent to (instead of `msg.sender`)\n@param amount of tokens to withdraw\n@param sendToMainnet if the tokens should be sent to mainnet or only withdrawn into sidechain address\n@param signature from the member, see `signatureIsValid` how signature generated for unlimited amount" + }, + "functionSelector": "1a79246c", + "id": 2339, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdrawToSigned", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2317, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2308, + "mutability": "mutable", + "name": "fromSigner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2339, + "src": "15450:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2307, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "15450:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2310, + "mutability": "mutable", + "name": "to", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2339, + "src": "15478:10:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2309, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "15478:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2312, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2339, + "src": "15498:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2311, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "15498:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2314, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2339, + "src": "15519:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2313, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "15519:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2316, + "mutability": "mutable", + "name": "signature", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2339, + "src": "15547:22:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 2315, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "15547:5:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "15440:135:4" + }, + "returnParameters": { + "id": 2320, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2319, + "mutability": "mutable", + "name": "withdrawn", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2339, + "src": "15608:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2318, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "15608:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "15607:16:4" + }, + "scope": 2432, + "src": "15415:377:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2347, + "nodeType": "Block", + "src": "15863:324:4", + "statements": [ + { + "AST": { + "nodeType": "YulBlock", + "src": "15882:299:4", + "statements": [ + { + "nodeType": "YulVariableDeclaration", + "src": "15896:20:4", + "value": { + "arguments": [ + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "15911:4:4", + "type": "", + "value": "0x40" + } + ], + "functionName": { + "name": "mload", + "nodeType": "YulIdentifier", + "src": "15905:5:4" + }, + "nodeType": "YulFunctionCall", + "src": "15905:11:4" + }, + "variables": [ + { + "name": "m", + "nodeType": "YulTypedName", + "src": "15900:1:4", + "type": "" + } + ] + }, + { + "nodeType": "YulAssignment", + "src": "15929:55:4", + "value": { + "arguments": [ + { + "name": "a", + "nodeType": "YulIdentifier", + "src": "15938:1:4" + }, + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "15941:42:4", + "type": "", + "value": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + } + ], + "functionName": { + "name": "and", + "nodeType": "YulIdentifier", + "src": "15934:3:4" + }, + "nodeType": "YulFunctionCall", + "src": "15934:50:4" + }, + "variableNames": [ + { + "name": "a", + "nodeType": "YulIdentifier", + "src": "15929:1:4" + } + ] + }, + { + "expression": { + "arguments": [ + { + "arguments": [ + { + "name": "m", + "nodeType": "YulIdentifier", + "src": "16025:1:4" + }, + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "16028:2:4", + "type": "", + "value": "20" + } + ], + "functionName": { + "name": "add", + "nodeType": "YulIdentifier", + "src": "16021:3:4" + }, + "nodeType": "YulFunctionCall", + "src": "16021:10:4" + }, + { + "arguments": [ + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "16053:44:4", + "type": "", + "value": "0x140000000000000000000000000000000000000000" + }, + { + "name": "a", + "nodeType": "YulIdentifier", + "src": "16099:1:4" + } + ], + "functionName": { + "name": "xor", + "nodeType": "YulIdentifier", + "src": "16049:3:4" + }, + "nodeType": "YulFunctionCall", + "src": "16049:52:4" + } + ], + "functionName": { + "name": "mstore", + "nodeType": "YulIdentifier", + "src": "15997:6:4" + }, + "nodeType": "YulFunctionCall", + "src": "15997:118:4" + }, + "nodeType": "YulExpressionStatement", + "src": "15997:118:4" + }, + { + "expression": { + "arguments": [ + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "16135:4:4", + "type": "", + "value": "0x40" + }, + { + "arguments": [ + { + "name": "m", + "nodeType": "YulIdentifier", + "src": "16145:1:4" + }, + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "16148:2:4", + "type": "", + "value": "52" + } + ], + "functionName": { + "name": "add", + "nodeType": "YulIdentifier", + "src": "16141:3:4" + }, + "nodeType": "YulFunctionCall", + "src": "16141:10:4" + } + ], + "functionName": { + "name": "mstore", + "nodeType": "YulIdentifier", + "src": "16128:6:4" + }, + "nodeType": "YulFunctionCall", + "src": "16128:24:4" + }, + "nodeType": "YulExpressionStatement", + "src": "16128:24:4" + }, + { + "nodeType": "YulAssignment", + "src": "16165:6:4", + "value": { + "name": "m", + "nodeType": "YulIdentifier", + "src": "16170:1:4" + }, + "variableNames": [ + { + "name": "b", + "nodeType": "YulIdentifier", + "src": "16165:1:4" + } + ] + } + ] + }, + "evmVersion": "istanbul", + "externalReferences": [ + { + "declaration": 2341, + "isOffset": false, + "isSlot": false, + "src": "15929:1:4", + "valueSize": 1 + }, + { + "declaration": 2341, + "isOffset": false, + "isSlot": false, + "src": "15938:1:4", + "valueSize": 1 + }, + { + "declaration": 2341, + "isOffset": false, + "isSlot": false, + "src": "16099:1:4", + "valueSize": 1 + }, + { + "declaration": 2344, + "isOffset": false, + "isSlot": false, + "src": "16165:1:4", + "valueSize": 1 + } + ], + "id": 2346, + "nodeType": "InlineAssembly", + "src": "15873:308:4" + } + ] + }, + "documentation": null, + "functionSelector": "593b79fe", + "id": 2348, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "toBytes", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2342, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2341, + "mutability": "mutable", + "name": "a", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2348, + "src": "15815:9:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2340, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "15815:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "15814:11:4" + }, + "returnParameters": { + "id": 2345, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2344, + "mutability": "mutable", + "name": "b", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2348, + "src": "15847:14:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 2343, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "15847:5:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "15846:16:4" + }, + "scope": 2432, + "src": "15798:389:4", + "stateMutability": "pure", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2430, + "nodeType": "Block", + "src": "16463:705:4", + "statements": [ + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 2364, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 2362, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "16477:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 2363, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "16487:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "16477:11:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 2367, + "nodeType": "IfStatement", + "src": "16473:25:4", + "trueBody": { + "expression": { + "argumentTypes": null, + "hexValue": "30", + "id": 2365, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "16497:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "functionReturnParameters": 2361, + "id": 2366, + "nodeType": "Return", + "src": "16490:8:4" + } + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 2373, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 2369, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "16516:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "<=", + "rightExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2371, + "name": "from", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2351, + "src": "16550:4:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 2370, + "name": "getWithdrawableEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1494, + "src": "16526:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 2372, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "16526:29:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "16516:39:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f696e73756666696369656e7442616c616e6365", + "id": 2374, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "16557:27:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_74799d65376d943bac023262731d6421d2e6b6332748a55385eb85a0683327fc", + "typeString": "literal_string \"error_insufficientBalance\"" + }, + "value": "error_insufficientBalance" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_74799d65376d943bac023262731d6421d2e6b6332748a55385eb85a0683327fc", + "typeString": "literal_string \"error_insufficientBalance\"" + } + ], + "id": 2368, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "16508:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2375, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "16508:77:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2376, + "nodeType": "ExpressionStatement", + "src": "16508:77:4" + }, + { + "assignments": [ + 2378 + ], + "declarations": [ + { + "constant": false, + "id": 2378, + "mutability": "mutable", + "name": "info", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2430, + "src": "16595:23:4", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + }, + "typeName": { + "contractScope": null, + "id": 2377, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "16595:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2382, + "initialValue": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 2379, + "name": "memberData", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1269, + "src": "16621:10:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo storage ref)" + } + }, + "id": 2381, + "indexExpression": { + "argumentTypes": null, + "id": 2380, + "name": "from", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2351, + "src": "16632:4:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "16621:16:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage", + "typeString": "struct DataUnionSidechain.MemberInfo storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "16595:42:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 2391, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2383, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2378, + "src": "16647:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 2385, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "withdrawnEarnings", + "nodeType": "MemberAccess", + "referencedDeclaration": 1244, + "src": "16647:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2389, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "16699:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2386, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2378, + "src": "16672:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 2387, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "withdrawnEarnings", + "nodeType": "MemberAccess", + "referencedDeclaration": 1244, + "src": "16672:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2388, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "16672:26:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 2390, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "16672:34:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "16647:59:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2392, + "nodeType": "ExpressionStatement", + "src": "16647:59:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 2398, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 2393, + "name": "totalEarningsWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1255, + "src": "16716:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2396, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "16768:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 2394, + "name": "totalEarningsWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1255, + "src": "16741:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2395, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "16741:26:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 2397, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "16741:34:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "16716:59:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2399, + "nodeType": "ExpressionStatement", + "src": "16716:59:4" + }, + { + "condition": { + "argumentTypes": null, + "id": 2400, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2357, + "src": "16789:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2416, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2353, + "src": "17062:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2417, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "17066:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 2414, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "17047:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "id": 2415, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "transfer", + "nodeType": "MemberAccess", + "referencedDeclaration": 4231, + "src": "17047:14:4", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", + "typeString": "function (address,uint256) external returns (bool)" + } + }, + "id": 2418, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "17047:26:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f7472616e73666572", + "id": 2419, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "17075:16:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + }, + "value": "error_transfer" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + } + ], + "id": 2413, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "17039:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2420, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "17039:53:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2421, + "nodeType": "ExpressionStatement", + "src": "17039:53:4" + }, + "id": 2422, + "nodeType": "IfStatement", + "src": "16785:307:4", + "trueBody": { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2404, + "name": "tokenMediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1249, + "src": "16884:13:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2405, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "16919:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2407, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2353, + "src": "16955:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 2406, + "name": "toBytes", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2348, + "src": "16947:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_address_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (address) pure returns (bytes memory)" + } + }, + "id": 2408, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "16947:11:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "expression": { + "argumentTypes": null, + "id": 2402, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "16841:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "id": 2403, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "transferAndCall", + "nodeType": "MemberAccess", + "referencedDeclaration": 2938, + "src": "16841:21:4", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$_t_bytes_memory_ptr_$returns$_t_bool_$", + "typeString": "function (address,uint256,bytes memory) external returns (bool)" + } + }, + "id": 2409, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "16841:135:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f7472616e73666572", + "id": 2410, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "16994:16:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + }, + "value": "error_transfer" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + } + ], + "id": 2401, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "16816:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2411, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "16816:208:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2412, + "nodeType": "ExpressionStatement", + "src": "16816:208:4" + } + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2424, + "name": "from", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2351, + "src": "17125:4:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2425, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "17131:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 2423, + "name": "EarningsWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1212, + "src": "17107:17:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 2426, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "17107:31:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2427, + "nodeType": "EmitStatement", + "src": "17102:36:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 2428, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "17155:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2361, + "id": 2429, + "nodeType": "Return", + "src": "17148:13:4" + } + ] + }, + "documentation": { + "id": 2349, + "nodeType": "StructuredDocumentation", + "src": "16193:140:4", + "text": "Internal function common to all withdraw methods.\nDoes NOT check proper access, so all callers must do that first." + }, + "id": 2431, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "_withdraw", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2358, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2351, + "mutability": "mutable", + "name": "from", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2431, + "src": "16357:12:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2350, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "16357:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2353, + "mutability": "mutable", + "name": "to", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2431, + "src": "16371:10:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2352, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "16371:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2355, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2431, + "src": "16383:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2354, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "16383:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2357, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2431, + "src": "16396:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2356, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "16396:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "16356:59:4" + }, + "returnParameters": { + "id": 2361, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2360, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2431, + "src": "16450:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2359, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "16450:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "16449:9:4" + }, + "scope": 2432, + "src": "16338:830:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "internal" + } + ], + "scope": 2433, + "src": "271:16899:4" + } + ], + "src": "0:17171:4" + }, + "legacyAST": { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/DataUnionSidechain.sol", + "exportedSymbols": { + "DataUnionSidechain": [ + 2432 + ] + }, + "id": 2433, + "nodeType": "SourceUnit", + "nodes": [ + { + "id": 1167, + "literals": [ + "solidity", + "0.6", + ".6" + ], + "nodeType": "PragmaDirective", + "src": "0:22:4" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "file": "openzeppelin-solidity/contracts/math/SafeMath.sol", + "id": 1168, + "nodeType": "ImportDirective", + "scope": 2433, + "sourceUnit": 3642, + "src": "24:59:4", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol", + "file": "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol", + "id": 1169, + "nodeType": "ImportDirective", + "scope": 2433, + "sourceUnit": 4283, + "src": "84:64:4", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/IERC677.sol", + "file": "./IERC677.sol", + "id": 1170, + "nodeType": "ImportDirective", + "scope": 2433, + "sourceUnit": 2940, + "src": "149:23:4", + "symbolAliases": [], + "unitAlias": "" + }, + { + "absolutePath": "/home/heynow/streamr/data-union-solidity/contracts/Ownable.sol", + "file": "./Ownable.sol", + "id": 1171, + "nodeType": "ImportDirective", + "scope": 2433, + "sourceUnit": 3181, + "src": "173:23:4", + "symbolAliases": [], + "unitAlias": "" + }, + { + "abstract": false, + "baseContracts": [ + { + "arguments": null, + "baseName": { + "contractScope": null, + "id": 1172, + "name": "Ownable", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 3180, + "src": "302:7:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_Ownable_$3180", + "typeString": "contract Ownable" + } + }, + "id": 1173, + "nodeType": "InheritanceSpecifier", + "src": "302:7:4" + } + ], + "contractDependencies": [ + 3180 + ], + "contractKind": "contract", + "documentation": null, + "fullyImplemented": true, + "id": 2432, + "linearizedBaseContracts": [ + 2432, + 3180 + ], + "name": "DataUnionSidechain", + "nodeType": "ContractDefinition", + "nodes": [ + { + "id": 1176, + "libraryName": { + "contractScope": null, + "id": 1174, + "name": "SafeMath", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 3641, + "src": "322:8:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_SafeMath_$3641", + "typeString": "library SafeMath" + } + }, + "nodeType": "UsingForDirective", + "src": "316:27:4", + "typeName": { + "id": 1175, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "335:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + }, + { + "canonicalName": "DataUnionSidechain.ActiveStatus", + "id": 1180, + "members": [ + { + "id": 1177, + "name": "None", + "nodeType": "EnumValue", + "src": "420:4:4" + }, + { + "id": 1178, + "name": "Active", + "nodeType": "EnumValue", + "src": "426:6:4" + }, + { + "id": 1179, + "name": "Inactive", + "nodeType": "EnumValue", + "src": "434:8:4" + } + ], + "name": "ActiveStatus", + "nodeType": "EnumDefinition", + "src": "401:42:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1184, + "name": "MemberJoined", + "nodeType": "EventDefinition", + "parameters": { + "id": 1183, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1182, + "indexed": true, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1184, + "src": "497:22:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1181, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "497:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "496:24:4" + }, + "src": "478:43:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1188, + "name": "MemberParted", + "nodeType": "EventDefinition", + "parameters": { + "id": 1187, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1186, + "indexed": true, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1188, + "src": "545:22:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1185, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "545:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "544:24:4" + }, + "src": "526:43:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1192, + "name": "JoinPartAgentAdded", + "nodeType": "EventDefinition", + "parameters": { + "id": 1191, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1190, + "indexed": true, + "mutability": "mutable", + "name": "agent", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1192, + "src": "599:21:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1189, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "599:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "598:23:4" + }, + "src": "574:48:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1196, + "name": "JoinPartAgentRemoved", + "nodeType": "EventDefinition", + "parameters": { + "id": 1195, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1194, + "indexed": true, + "mutability": "mutable", + "name": "agent", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1196, + "src": "654:21:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1193, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "654:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "653:23:4" + }, + "src": "627:50:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1200, + "name": "RevenueReceived", + "nodeType": "EventDefinition", + "parameters": { + "id": 1199, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1198, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1200, + "src": "741:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1197, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "741:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "740:16:4" + }, + "src": "719:38:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1206, + "name": "NewEarnings", + "nodeType": "EventDefinition", + "parameters": { + "id": 1205, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1202, + "indexed": false, + "mutability": "mutable", + "name": "earningsPerMember", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1206, + "src": "780:25:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1201, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "780:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1204, + "indexed": false, + "mutability": "mutable", + "name": "activeMemberCount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1206, + "src": "807:25:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1203, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "807:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "779:54:4" + }, + "src": "762:72:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1212, + "name": "EarningsWithdrawn", + "nodeType": "EventDefinition", + "parameters": { + "id": 1211, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1208, + "indexed": true, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1212, + "src": "892:22:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1207, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "892:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1210, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1212, + "src": "916:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1209, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "916:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "891:40:4" + }, + "src": "868:64:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1220, + "name": "TransferWithinContract", + "nodeType": "EventDefinition", + "parameters": { + "id": 1219, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1214, + "indexed": true, + "mutability": "mutable", + "name": "from", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1220, + "src": "995:20:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1213, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "995:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1216, + "indexed": true, + "mutability": "mutable", + "name": "to", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1220, + "src": "1017:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1215, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1017:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1218, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1220, + "src": "1037:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1217, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1037:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "994:55:4" + }, + "src": "966:84:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1228, + "name": "TransferToAddressInContract", + "nodeType": "EventDefinition", + "parameters": { + "id": 1227, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1222, + "indexed": true, + "mutability": "mutable", + "name": "from", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1228, + "src": "1089:20:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1221, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1089:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1224, + "indexed": true, + "mutability": "mutable", + "name": "to", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1228, + "src": "1111:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1223, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1111:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1226, + "indexed": false, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1228, + "src": "1131:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1225, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1131:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1088:55:4" + }, + "src": "1055:89:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1232, + "name": "UpdateNewMemberEth", + "nodeType": "EventDefinition", + "parameters": { + "id": 1231, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1230, + "indexed": false, + "mutability": "mutable", + "name": "value", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1232, + "src": "1196:10:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1229, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1196:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1195:12:4" + }, + "src": "1171:37:4" + }, + { + "anonymous": false, + "documentation": null, + "id": 1236, + "name": "NewMemberEthSent", + "nodeType": "EventDefinition", + "parameters": { + "id": 1235, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1234, + "indexed": false, + "mutability": "mutable", + "name": "amountWei", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1236, + "src": "1236:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1233, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "1236:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "1235:16:4" + }, + "src": "1213:39:4" + }, + { + "canonicalName": "DataUnionSidechain.MemberInfo", + "id": 1245, + "members": [ + { + "constant": false, + "id": 1238, + "mutability": "mutable", + "name": "status", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1245, + "src": "1286:19:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "typeName": { + "contractScope": null, + "id": 1237, + "name": "ActiveStatus", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1180, + "src": "1286:12:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1240, + "mutability": "mutable", + "name": "earningsBeforeLastJoin", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1245, + "src": "1315:30:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1239, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1315:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1242, + "mutability": "mutable", + "name": "lmeAtJoin", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1245, + "src": "1355:17:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1241, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1355:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1244, + "mutability": "mutable", + "name": "withdrawnEarnings", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1245, + "src": "1382:25:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1243, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1382:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "name": "MemberInfo", + "nodeType": "StructDefinition", + "scope": 2432, + "src": "1258:156:4", + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "fc0c546a", + "id": 1247, + "mutability": "mutable", + "name": "token", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1420:20:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + }, + "typeName": { + "contractScope": null, + "id": 1246, + "name": "IERC677", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 2939, + "src": "1420:7:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "cc772440", + "id": 1249, + "mutability": "mutable", + "name": "tokenMediator", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1446:28:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1248, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1446:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "bf1e42c0", + "id": 1251, + "mutability": "mutable", + "name": "dataUnionMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1480:31:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1250, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1480:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "2df3eba4", + "id": 1253, + "mutability": "mutable", + "name": "totalEarnings", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1518:28:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1252, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1518:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "85a21246", + "id": 1255, + "mutability": "mutable", + "name": "totalEarningsWithdrawn", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1552:37:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1254, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1552:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "5fb6c6ed", + "id": 1257, + "mutability": "mutable", + "name": "activeMemberCount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1596:32:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1256, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1596:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "3d8e36a3", + "id": 1259, + "mutability": "mutable", + "name": "inactiveMemberCount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1634:34:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1258, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1634:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "6d8018b8", + "id": 1261, + "mutability": "mutable", + "name": "lifetimeMemberEarnings", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1674:37:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1260, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1674:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "3ebff90e", + "id": 1263, + "mutability": "mutable", + "name": "joinPartAgentCount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1718:33:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1262, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1718:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "79049017", + "id": 1265, + "mutability": "mutable", + "name": "newMemberEth", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1758:27:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1264, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "1758:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "9107d08e", + "id": 1269, + "mutability": "mutable", + "name": "memberData", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1792:48:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo)" + }, + "typeName": { + "id": 1268, + "keyType": { + "id": 1266, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1800:7:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "1792:30:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo)" + }, + "valueType": { + "contractScope": null, + "id": 1267, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "1811:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + } + }, + "value": null, + "visibility": "public" + }, + { + "constant": false, + "functionSelector": "c44b73a3", + "id": 1273, + "mutability": "mutable", + "name": "joinPartAgents", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2432, + "src": "1846:54:4", + "stateVariable": true, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + }, + "typeName": { + "id": 1272, + "keyType": { + "id": 1270, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "1854:7:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Mapping", + "src": "1846:32:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + }, + "valueType": { + "contractScope": null, + "id": 1271, + "name": "ActiveStatus", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1180, + "src": "1865:12:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + } + }, + "value": null, + "visibility": "public" + }, + { + "body": { + "id": 1287, + "nodeType": "Block", + "src": "1936:113:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1282, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1276, + "name": "joinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1273, + "src": "1954:14:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1279, + "indexExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1277, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "1969:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1278, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "1969:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "1954:26:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1280, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "1984:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1281, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "1984:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "1954:49:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6f6e6c794a6f696e506172744167656e74", + "id": 1283, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2005:25:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_956f78fcce2b1e1b69129df43faa6544de07d17c942f714c7b52da05de24f86e", + "typeString": "literal_string \"error_onlyJoinPartAgent\"" + }, + "value": "error_onlyJoinPartAgent" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_956f78fcce2b1e1b69129df43faa6544de07d17c942f714c7b52da05de24f86e", + "typeString": "literal_string \"error_onlyJoinPartAgent\"" + } + ], + "id": 1275, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "1946:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1284, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "1946:85:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1285, + "nodeType": "ExpressionStatement", + "src": "1946:85:4" + }, + { + "id": 1286, + "nodeType": "PlaceholderStatement", + "src": "2041:1:4" + } + ] + }, + "documentation": null, + "id": 1288, + "name": "onlyJoinPartAgent", + "nodeType": "ModifierDefinition", + "overrides": null, + "parameters": { + "id": 1274, + "nodeType": "ParameterList", + "parameters": [], + "src": "1933:2:4" + }, + "src": "1907:142:4", + "virtual": false, + "visibility": "internal" + }, + { + "body": { + "id": 1297, + "nodeType": "Block", + "src": "2137:2:4", + "statements": [] + }, + "documentation": null, + "id": 1298, + "implemented": true, + "kind": "constructor", + "modifiers": [ + { + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "30", + "id": 1293, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2133:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "id": 1292, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2125:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1291, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2125:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1294, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2125:10:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "id": 1295, + "modifierName": { + "argumentTypes": null, + "id": 1290, + "name": "Ownable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3180, + "src": "2117:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_Ownable_$3180_$", + "typeString": "type(contract Ownable)" + } + }, + "nodeType": "ModifierInvocation", + "src": "2117:19:4" + } + ], + "name": "", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1289, + "nodeType": "ParameterList", + "parameters": [], + "src": "2107:2:4" + }, + "returnParameters": { + "id": 1296, + "nodeType": "ParameterList", + "parameters": [], + "src": "2137:0:4" + }, + "scope": 2432, + "src": "2096:43:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1301, + "nodeType": "Block", + "src": "2172:2:4", + "statements": [] + }, + "documentation": null, + "id": 1302, + "implemented": true, + "kind": "receive", + "modifiers": [], + "name": "", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1299, + "nodeType": "ParameterList", + "parameters": [], + "src": "2152:2:4" + }, + "returnParameters": { + "id": 1300, + "nodeType": "ParameterList", + "parameters": [], + "src": "2172:0:4" + }, + "scope": 2432, + "src": "2145:29:4", + "stateMutability": "payable", + "virtual": false, + "visibility": "external" + }, + { + "body": { + "id": 1356, + "nodeType": "Block", + "src": "2437:456:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1321, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "!", + "prefix": true, + "src": "2455:16:4", + "subExpression": { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1319, + "name": "isInitialized", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1373, + "src": "2456:13:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_bool_$", + "typeString": "function () view returns (bool)" + } + }, + "id": 1320, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2456:15:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f616c7265616479496e697469616c697a6564", + "id": 1322, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2473:26:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_845b975974cd10e32a9d060d8158a7b328a11a448ea3c75e0603113d3b173fd5", + "typeString": "literal_string \"error_alreadyInitialized\"" + }, + "value": "error_alreadyInitialized" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_845b975974cd10e32a9d060d8158a7b328a11a448ea3c75e0603113d3b173fd5", + "typeString": "literal_string \"error_alreadyInitialized\"" + } + ], + "id": 1318, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "2447:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1323, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2447:53:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1324, + "nodeType": "ExpressionStatement", + "src": "2447:53:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1328, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1325, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "2510:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1326, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "2518:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1327, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "2518:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "src": "2510:18:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 1329, + "nodeType": "ExpressionStatement", + "src": "2510:18:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1334, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1330, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "2633:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1332, + "name": "tokenAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1306, + "src": "2649:12:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1331, + "name": "IERC677", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2939, + "src": "2641:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_contract$_IERC677_$2939_$", + "typeString": "type(contract IERC677)" + } + }, + "id": 1333, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2641:21:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "src": "2633:29:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "id": 1335, + "nodeType": "ExpressionStatement", + "src": "2633:29:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1337, + "name": "initialJoinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1309, + "src": "2690:21:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + ], + "id": 1336, + "name": "addJoinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1533, + "src": "2672:17:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_array$_t_address_$dyn_memory_ptr_$returns$__$", + "typeString": "function (address[] memory)" + } + }, + "id": 1338, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2672:40:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1339, + "nodeType": "ExpressionStatement", + "src": "2672:40:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1342, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1340, + "name": "tokenMediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1249, + "src": "2722:13:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 1341, + "name": "tokenMediatorAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1311, + "src": "2738:20:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "2722:36:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 1343, + "nodeType": "ExpressionStatement", + "src": "2722:36:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1346, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1344, + "name": "dataUnionMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1251, + "src": "2768:16:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 1345, + "name": "mainnetDataUnionAddress", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1313, + "src": "2787:23:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "2768:42:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 1347, + "nodeType": "ExpressionStatement", + "src": "2768:42:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1349, + "name": "defaultNewMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1315, + "src": "2836:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1348, + "name": "setNewMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1411, + "src": "2820:15:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 1350, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2820:36:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1351, + "nodeType": "ExpressionStatement", + "src": "2820:36:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1354, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1352, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "2866:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 1353, + "name": "initialOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1304, + "src": "2874:12:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "2866:20:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 1355, + "nodeType": "ExpressionStatement", + "src": "2866:20:4" + } + ] + }, + "documentation": null, + "functionSelector": "015c7f51", + "id": 1357, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "initialize", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1316, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1304, + "mutability": "mutable", + "name": "initialOwner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1357, + "src": "2209:20:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1303, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2209:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1306, + "mutability": "mutable", + "name": "tokenAddress", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1357, + "src": "2239:20:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1305, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2239:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1309, + "mutability": "mutable", + "name": "initialJoinPartAgents", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1357, + "src": "2269:38:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 1307, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2269:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 1308, + "length": null, + "nodeType": "ArrayTypeName", + "src": "2269:9:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1311, + "mutability": "mutable", + "name": "tokenMediatorAddress", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1357, + "src": "2317:28:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1310, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2317:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1313, + "mutability": "mutable", + "name": "mainnetDataUnionAddress", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1357, + "src": "2355:31:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1312, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2355:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1315, + "mutability": "mutable", + "name": "defaultNewMemberEth", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1357, + "src": "2396:27:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1314, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "2396:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2199:230:4" + }, + "returnParameters": { + "id": 1317, + "nodeType": "ParameterList", + "parameters": [], + "src": "2437:0:4" + }, + "scope": 2432, + "src": "2180:713:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1372, + "nodeType": "Block", + "src": "2950:52:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 1370, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1364, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "2975:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + ], + "id": 1363, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2967:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1362, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2967:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1365, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2967:14:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "30", + "id": 1368, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "2993:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + } + ], + "id": 1367, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "2985:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1366, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "2985:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1369, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "2985:10:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "src": "2967:28:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "functionReturnParameters": 1361, + "id": 1371, + "nodeType": "Return", + "src": "2960:35:4" + } + ] + }, + "documentation": null, + "functionSelector": "392e53cd", + "id": 1373, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "isInitialized", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1358, + "nodeType": "ParameterList", + "parameters": [], + "src": "2921:2:4" + }, + "returnParameters": { + "id": 1361, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1360, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1373, + "src": "2945:4:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 1359, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "2945:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "2944:6:4" + }, + "scope": 2432, + "src": "2899:103:4", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1389, + "nodeType": "Block", + "src": "3235:229:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "components": [ + { + "argumentTypes": null, + "id": 1381, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1253, + "src": "3266:13:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 1382, + "name": "totalEarningsWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1255, + "src": "3293:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 1383, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "3329:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 1384, + "name": "inactiveMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1259, + "src": "3360:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 1385, + "name": "lifetimeMemberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1261, + "src": "3393:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 1386, + "name": "joinPartAgentCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1263, + "src": "3429:18:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "id": 1387, + "isConstant": false, + "isInlineArray": true, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "3252:205:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint256_$6_memory_ptr", + "typeString": "uint256[6] memory" + } + }, + "functionReturnParameters": 1380, + "id": 1388, + "nodeType": "Return", + "src": "3245:212:4" + } + ] + }, + "documentation": { + "id": 1374, + "nodeType": "StructuredDocumentation", + "src": "3008:162:4", + "text": "Atomic getter to get all state variables in one call\nThis alleviates the fact that JSON RPC batch requests aren't available in ethers.js" + }, + "functionSelector": "c59d4847", + "id": 1390, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getStats", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1375, + "nodeType": "ParameterList", + "parameters": [], + "src": "3192:2:4" + }, + "returnParameters": { + "id": 1380, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1379, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1390, + "src": "3216:17:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint256_$6_memory_ptr", + "typeString": "uint256[6]" + }, + "typeName": { + "baseType": { + "id": 1376, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "3216:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1378, + "length": { + "argumentTypes": null, + "hexValue": "36", + "id": 1377, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3224:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_6_by_1", + "typeString": "int_const 6" + }, + "value": "6" + }, + "nodeType": "ArrayTypeName", + "src": "3216:10:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_uint256_$6_storage_ptr", + "typeString": "uint256[6]" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "3215:19:4" + }, + "scope": 2432, + "src": "3175:289:4", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1410, + "nodeType": "Block", + "src": "3522:113:4", + "statements": [ + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1399, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1397, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1392, + "src": "3535:3:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 1398, + "name": "newMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1265, + "src": "3542:12:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "3535:19:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 1401, + "nodeType": "IfStatement", + "src": "3532:31:4", + "trueBody": { + "expression": null, + "functionReturnParameters": 1396, + "id": 1400, + "nodeType": "Return", + "src": "3556:7:4" + } + }, + { + "expression": { + "argumentTypes": null, + "id": 1404, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1402, + "name": "newMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1265, + "src": "3572:12:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 1403, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1392, + "src": "3587:3:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "3572:18:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1405, + "nodeType": "ExpressionStatement", + "src": "3572:18:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1407, + "name": "val", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1392, + "src": "3624:3:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1406, + "name": "UpdateNewMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1232, + "src": "3605:18:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 1408, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3605:23:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1409, + "nodeType": "EmitStatement", + "src": "3600:28:4" + } + ] + }, + "documentation": null, + "functionSelector": "e6018c31", + "id": 1411, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 1395, + "modifierName": { + "argumentTypes": null, + "id": 1394, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "3512:9:4", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "3512:9:4" + } + ], + "name": "setNewMemberEth", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1393, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1392, + "mutability": "mutable", + "name": "val", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1411, + "src": "3495:8:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1391, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "3495:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "3494:10:4" + }, + "returnParameters": { + "id": 1396, + "nodeType": "ParameterList", + "parameters": [], + "src": "3522:0:4" + }, + "scope": 2432, + "src": "3470:165:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1450, + "nodeType": "Block", + "src": "3708:357:4", + "statements": [ + { + "assignments": [ + 1419 + ], + "declarations": [ + { + "constant": false, + "id": 1419, + "mutability": "mutable", + "name": "info", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1450, + "src": "3718:23:4", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + }, + "typeName": { + "contractScope": null, + "id": 1418, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "3718:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1423, + "initialValue": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1420, + "name": "memberData", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1269, + "src": "3744:10:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo storage ref)" + } + }, + "id": 1422, + "indexExpression": { + "argumentTypes": null, + "id": 1421, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1413, + "src": "3755:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "3744:18:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage", + "typeString": "struct DataUnionSidechain.MemberInfo storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "3718:44:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1429, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1425, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1419, + "src": "3780:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1426, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "3780:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1427, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "3795:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1428, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "None", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "3795:17:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "3780:32:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6e6f744d656d626572", + "id": 1430, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "3814:17:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_d7a3e0a08d346fd90b7cdd70bcb3d747f808a83a9bae19592a90bf66cd6b4372", + "typeString": "literal_string \"error_notMember\"" + }, + "value": "error_notMember" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_d7a3e0a08d346fd90b7cdd70bcb3d747f808a83a9bae19592a90bf66cd6b4372", + "typeString": "literal_string \"error_notMember\"" + } + ], + "id": 1424, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "3772:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1431, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3772:60:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1432, + "nodeType": "ExpressionStatement", + "src": "3772:60:4" + }, + { + "expression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1448, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1433, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1419, + "src": "3861:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1434, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "earningsBeforeLastJoin", + "nodeType": "MemberAccess", + "referencedDeclaration": 1240, + "src": "3861:27:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "+", + "rightExpression": { + "argumentTypes": null, + "components": [ + { + "argumentTypes": null, + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1439, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1435, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1419, + "src": "3921:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1436, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "3921:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1437, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "3936:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1438, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "3936:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "3921:34:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1445, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4043:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "id": 1446, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "Conditional", + "src": "3921:123:4", + "trueExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1442, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1419, + "src": "4005:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1443, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "lmeAtJoin", + "nodeType": "MemberAccess", + "referencedDeclaration": 1242, + "src": "4005:14:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1440, + "name": "lifetimeMemberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1261, + "src": "3978:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1441, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "3978:26:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1444, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "3978:42:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "id": 1447, + "isConstant": false, + "isInlineArray": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "TupleExpression", + "src": "3903:155:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "3861:197:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 1417, + "id": 1449, + "nodeType": "Return", + "src": "3842:216:4" + } + ] + }, + "documentation": null, + "functionSelector": "131b9c04", + "id": 1451, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getEarnings", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1414, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1413, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1451, + "src": "3662:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1412, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "3662:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "3661:16:4" + }, + "returnParameters": { + "id": 1417, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1416, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1451, + "src": "3699:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1415, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "3699:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "3698:9:4" + }, + "scope": 2432, + "src": "3641:424:4", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1476, + "nodeType": "Block", + "src": "4139:170:4", + "statements": [ + { + "assignments": [ + 1459 + ], + "declarations": [ + { + "constant": false, + "id": 1459, + "mutability": "mutable", + "name": "info", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1476, + "src": "4149:23:4", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + }, + "typeName": { + "contractScope": null, + "id": 1458, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "4149:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1463, + "initialValue": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1460, + "name": "memberData", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1269, + "src": "4175:10:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo storage ref)" + } + }, + "id": 1462, + "indexExpression": { + "argumentTypes": null, + "id": 1461, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1453, + "src": "4186:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "4175:18:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage", + "typeString": "struct DataUnionSidechain.MemberInfo storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "4149:44:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1469, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1465, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1459, + "src": "4211:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1466, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "4211:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1467, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "4226:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1468, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "None", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "4226:17:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "4211:32:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6e6f744d656d626572", + "id": 1470, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4245:17:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_d7a3e0a08d346fd90b7cdd70bcb3d747f808a83a9bae19592a90bf66cd6b4372", + "typeString": "literal_string \"error_notMember\"" + }, + "value": "error_notMember" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_d7a3e0a08d346fd90b7cdd70bcb3d747f808a83a9bae19592a90bf66cd6b4372", + "typeString": "literal_string \"error_notMember\"" + } + ], + "id": 1464, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "4203:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1471, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4203:60:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1472, + "nodeType": "ExpressionStatement", + "src": "4203:60:4" + }, + { + "expression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1473, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1459, + "src": "4280:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1474, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "withdrawnEarnings", + "nodeType": "MemberAccess", + "referencedDeclaration": 1244, + "src": "4280:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 1457, + "id": 1475, + "nodeType": "Return", + "src": "4273:29:4" + } + ] + }, + "documentation": null, + "functionSelector": "ae66d948", + "id": 1477, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getWithdrawn", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1454, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1453, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1477, + "src": "4093:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1452, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4093:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4092:16:4" + }, + "returnParameters": { + "id": 1457, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1456, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1477, + "src": "4130:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1455, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4130:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4129:9:4" + }, + "scope": 2432, + "src": "4071:238:4", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1493, + "nodeType": "Block", + "src": "4394:69:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1489, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1479, + "src": "4448:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1488, + "name": "getWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1477, + "src": "4435:12:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 1490, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4435:20:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1485, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1479, + "src": "4423:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1484, + "name": "getEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1451, + "src": "4411:11:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 1486, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4411:19:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1487, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "4411:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1491, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4411:45:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 1483, + "id": 1492, + "nodeType": "Return", + "src": "4404:52:4" + } + ] + }, + "documentation": null, + "functionSelector": "2e0d4212", + "id": 1494, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "getWithdrawableEarnings", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1480, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1479, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1494, + "src": "4348:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1478, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4348:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4347:16:4" + }, + "returnParameters": { + "id": 1483, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1482, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1494, + "src": "4385:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1481, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4385:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4384:9:4" + }, + "scope": 2432, + "src": "4315:148:4", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1504, + "nodeType": "Block", + "src": "4528:65:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1501, + "name": "totalEarningsWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1255, + "src": "4563:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1499, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1253, + "src": "4545:13:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1500, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "4545:17:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1502, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4545:41:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 1498, + "id": 1503, + "nodeType": "Return", + "src": "4538:48:4" + } + ] + }, + "documentation": null, + "functionSelector": "0600a865", + "id": 1505, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "totalWithdrawable", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1495, + "nodeType": "ParameterList", + "parameters": [], + "src": "4495:2:4" + }, + "returnParameters": { + "id": 1498, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1497, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1505, + "src": "4519:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1496, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4519:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4518:9:4" + }, + "scope": 2432, + "src": "4469:124:4", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1532, + "nodeType": "Block", + "src": "4668:112:4", + "statements": [ + { + "body": { + "id": 1530, + "nodeType": "Block", + "src": "4722:52:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1525, + "name": "agents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1508, + "src": "4753:6:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + }, + "id": 1527, + "indexExpression": { + "argumentTypes": null, + "id": 1526, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1514, + "src": "4760:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "4753:9:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1524, + "name": "addJoinPartAgent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1569, + "src": "4736:16:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 1528, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4736:27:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1529, + "nodeType": "ExpressionStatement", + "src": "4736:27:4" + } + ] + }, + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1520, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1517, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1514, + "src": "4698:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "<", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1518, + "name": "agents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1508, + "src": "4702:6:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + }, + "id": 1519, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "length", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "4702:13:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "4698:17:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 1531, + "initializationExpression": { + "assignments": [ + 1514 + ], + "declarations": [ + { + "constant": false, + "id": 1514, + "mutability": "mutable", + "name": "i", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1531, + "src": "4683:9:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1513, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "4683:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1516, + "initialValue": { + "argumentTypes": null, + "hexValue": "30", + "id": 1515, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4695:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "nodeType": "VariableDeclarationStatement", + "src": "4683:13:4" + }, + "loopExpression": { + "expression": { + "argumentTypes": null, + "id": 1522, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "++", + "prefix": false, + "src": "4717:3:4", + "subExpression": { + "argumentTypes": null, + "id": 1521, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1514, + "src": "4717:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1523, + "nodeType": "ExpressionStatement", + "src": "4717:3:4" + }, + "nodeType": "ForStatement", + "src": "4678:96:4" + } + ] + }, + "documentation": null, + "functionSelector": "1796621a", + "id": 1533, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 1511, + "modifierName": { + "argumentTypes": null, + "id": 1510, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "4658:9:4", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "4658:9:4" + } + ], + "name": "addJoinPartAgents", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1509, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1508, + "mutability": "mutable", + "name": "agents", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1533, + "src": "4626:23:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 1506, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4626:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 1507, + "length": null, + "nodeType": "ArrayTypeName", + "src": "4626:9:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4625:25:4" + }, + "returnParameters": { + "id": 1512, + "nodeType": "ParameterList", + "parameters": [], + "src": "4668:0:4" + }, + "scope": 2432, + "src": "4599:181:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1568, + "nodeType": "Block", + "src": "4844:247:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1546, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1541, + "name": "joinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1273, + "src": "4862:14:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1543, + "indexExpression": { + "argumentTypes": null, + "id": 1542, + "name": "agent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1535, + "src": "4877:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "4862:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1544, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "4887:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1545, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "4887:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "4862:44:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f616c72656164794163746976654167656e74", + "id": 1547, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "4908:26:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_bb39e853702abde1810741d4774a63438edaeee888b9b2a936f664fafe2a45b8", + "typeString": "literal_string \"error_alreadyActiveAgent\"" + }, + "value": "error_alreadyActiveAgent" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_bb39e853702abde1810741d4774a63438edaeee888b9b2a936f664fafe2a45b8", + "typeString": "literal_string \"error_alreadyActiveAgent\"" + } + ], + "id": 1540, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "4854:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1548, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "4854:81:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1549, + "nodeType": "ExpressionStatement", + "src": "4854:81:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1555, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1550, + "name": "joinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1273, + "src": "4945:14:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1552, + "indexExpression": { + "argumentTypes": null, + "id": 1551, + "name": "agent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1535, + "src": "4960:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "4945:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1553, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "4969:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1554, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "4969:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "4945:43:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "id": 1556, + "nodeType": "ExpressionStatement", + "src": "4945:43:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1558, + "name": "agent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1535, + "src": "5022:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1557, + "name": "JoinPartAgentAdded", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1192, + "src": "5003:18:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 1559, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5003:25:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1560, + "nodeType": "EmitStatement", + "src": "4998:30:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1566, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1561, + "name": "joinPartAgentCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1263, + "src": "5038:18:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "31", + "id": 1564, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5082:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "argumentTypes": null, + "id": 1562, + "name": "joinPartAgentCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1263, + "src": "5059:18:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1563, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "5059:22:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1565, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5059:25:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "5038:46:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1567, + "nodeType": "ExpressionStatement", + "src": "5038:46:4" + } + ] + }, + "documentation": null, + "functionSelector": "662d45a2", + "id": 1569, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 1538, + "modifierName": { + "argumentTypes": null, + "id": 1537, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "4834:9:4", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "4834:9:4" + } + ], + "name": "addJoinPartAgent", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1536, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1535, + "mutability": "mutable", + "name": "agent", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1569, + "src": "4812:13:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1534, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "4812:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "4811:15:4" + }, + "returnParameters": { + "id": 1539, + "nodeType": "ParameterList", + "parameters": [], + "src": "4844:0:4" + }, + "scope": 2432, + "src": "4786:305:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1604, + "nodeType": "Block", + "src": "5158:247:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1582, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1577, + "name": "joinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1273, + "src": "5176:14:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1579, + "indexExpression": { + "argumentTypes": null, + "id": 1578, + "name": "agent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1571, + "src": "5191:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "5176:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1580, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "5201:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1581, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "5201:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "5176:44:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6e6f744163746976654167656e74", + "id": 1583, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5222:22:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_7374b1c602a903c4ee0ecf07cf7d2bb5da07e6616a0fd70b600a11d82b670e51", + "typeString": "literal_string \"error_notActiveAgent\"" + }, + "value": "error_notActiveAgent" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_7374b1c602a903c4ee0ecf07cf7d2bb5da07e6616a0fd70b600a11d82b670e51", + "typeString": "literal_string \"error_notActiveAgent\"" + } + ], + "id": 1576, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "5168:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1584, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5168:77:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1585, + "nodeType": "ExpressionStatement", + "src": "5168:77:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1591, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1586, + "name": "joinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1273, + "src": "5255:14:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1588, + "indexExpression": { + "argumentTypes": null, + "id": 1587, + "name": "agent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1571, + "src": "5270:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "nodeType": "IndexAccess", + "src": "5255:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1589, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "5279:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1590, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Inactive", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "5279:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "5255:45:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "id": 1592, + "nodeType": "ExpressionStatement", + "src": "5255:45:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1594, + "name": "agent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1571, + "src": "5336:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1593, + "name": "JoinPartAgentRemoved", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1196, + "src": "5315:20:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 1595, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5315:27:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1596, + "nodeType": "EmitStatement", + "src": "5310:32:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1602, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1597, + "name": "joinPartAgentCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1263, + "src": "5352:18:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "31", + "id": 1600, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5396:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "argumentTypes": null, + "id": 1598, + "name": "joinPartAgentCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1263, + "src": "5373:18:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1599, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "5373:22:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1601, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5373:25:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "5352:46:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1603, + "nodeType": "ExpressionStatement", + "src": "5352:46:4" + } + ] + }, + "documentation": null, + "functionSelector": "09a6400b", + "id": 1605, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 1574, + "modifierName": { + "argumentTypes": null, + "id": 1573, + "name": "onlyOwner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3137, + "src": "5148:9:4", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "5148:9:4" + } + ], + "name": "removeJoinPartAgent", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1572, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1571, + "mutability": "mutable", + "name": "agent", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1605, + "src": "5126:13:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1570, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "5126:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "5125:15:4" + }, + "returnParameters": { + "id": 1575, + "nodeType": "ParameterList", + "parameters": [], + "src": "5158:0:4" + }, + "scope": 2432, + "src": "5097:308:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1671, + "nodeType": "Block", + "src": "5606:542:4", + "statements": [ + { + "assignments": [ + 1612 + ], + "declarations": [ + { + "constant": false, + "id": 1612, + "mutability": "mutable", + "name": "balance", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1671, + "src": "5616:15:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1611, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "5616:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1620, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1617, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "5658:4:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + ], + "id": 1616, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "5650:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1615, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "5650:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1618, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5650:13:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "expression": { + "argumentTypes": null, + "id": 1613, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "5634:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "id": 1614, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "balanceOf", + "nodeType": "MemberAccess", + "referencedDeclaration": 4221, + "src": "5634:15:4", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view external returns (uint256)" + } + }, + "id": 1619, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5634:30:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "5616:48:4" + }, + { + "assignments": [ + 1622 + ], + "declarations": [ + { + "constant": false, + "id": 1622, + "mutability": "mutable", + "name": "revenue", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1671, + "src": "5674:15:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1621, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "5674:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1628, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [], + "expression": { + "argumentTypes": [], + "id": 1625, + "name": "totalWithdrawable", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1505, + "src": "5704:17:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$__$returns$_t_uint256_$", + "typeString": "function () view returns (uint256)" + } + }, + "id": 1626, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5704:19:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1623, + "name": "balance", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1612, + "src": "5692:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1624, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "5692:11:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1627, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5692:32:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "5674:50:4" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 1635, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1631, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1629, + "name": "revenue", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1622, + "src": "5766:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1630, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5777:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "5766:12:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "||", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1634, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1632, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "5782:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1633, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5803:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "5782:22:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "5766:38:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 1638, + "nodeType": "IfStatement", + "src": "5762:52:4", + "trueBody": { + "expression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1636, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "5813:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "functionReturnParameters": 1610, + "id": 1637, + "nodeType": "Return", + "src": "5806:8:4" + } + }, + { + "assignments": [ + 1640 + ], + "declarations": [ + { + "constant": false, + "id": 1640, + "mutability": "mutable", + "name": "earningsPerMember", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1671, + "src": "5824:25:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1639, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "5824:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1645, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1643, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "5864:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1641, + "name": "revenue", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1622, + "src": "5852:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1642, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "div", + "nodeType": "MemberAccess", + "referencedDeclaration": 3571, + "src": "5852:11:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1644, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5852:30:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "5824:58:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1651, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1646, + "name": "lifetimeMemberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1261, + "src": "5892:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1649, + "name": "earningsPerMember", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1640, + "src": "5944:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1647, + "name": "lifetimeMemberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1261, + "src": "5917:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1648, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "5917:26:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1650, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5917:45:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "5892:70:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1652, + "nodeType": "ExpressionStatement", + "src": "5892:70:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1658, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1653, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1253, + "src": "5972:13:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1656, + "name": "revenue", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1622, + "src": "6006:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1654, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1253, + "src": "5988:13:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1655, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "5988:17:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1657, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "5988:26:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "5972:42:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1659, + "nodeType": "ExpressionStatement", + "src": "5972:42:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1661, + "name": "revenue", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1622, + "src": "6045:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1660, + "name": "RevenueReceived", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1200, + "src": "6029:15:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 1662, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6029:24:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1663, + "nodeType": "EmitStatement", + "src": "6024:29:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1665, + "name": "earningsPerMember", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1640, + "src": "6080:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 1666, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "6099:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1664, + "name": "NewEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1206, + "src": "6068:11:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$_t_uint256_$returns$__$", + "typeString": "function (uint256,uint256)" + } + }, + "id": 1667, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6068:49:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1668, + "nodeType": "EmitStatement", + "src": "6063:54:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1669, + "name": "revenue", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1622, + "src": "6134:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 1610, + "id": 1670, + "nodeType": "Return", + "src": "6127:14:4" + } + ] + }, + "documentation": { + "id": 1606, + "nodeType": "StructuredDocumentation", + "src": "5411:139:4", + "text": "Process unaccounted tokens that have been sent previously\nCalled by AMB (see DataUnionMainnet:sendTokensToBridge)" + }, + "functionSelector": "331beb5f", + "id": 1672, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "refreshRevenue", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1607, + "nodeType": "ParameterList", + "parameters": [], + "src": "5578:2:4" + }, + "returnParameters": { + "id": 1610, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1609, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1672, + "src": "5597:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1608, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "5597:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "5596:9:4" + }, + "scope": 2432, + "src": "5555:593:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1765, + "nodeType": "Block", + "src": "6222:765:4", + "statements": [ + { + "assignments": [ + 1680 + ], + "declarations": [ + { + "constant": false, + "id": 1680, + "mutability": "mutable", + "name": "info", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1765, + "src": "6232:23:4", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + }, + "typeName": { + "contractScope": null, + "id": 1679, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "6232:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1684, + "initialValue": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1681, + "name": "memberData", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1269, + "src": "6258:10:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo storage ref)" + } + }, + "id": 1683, + "indexExpression": { + "argumentTypes": null, + "id": 1682, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1674, + "src": "6269:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "6258:18:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage", + "typeString": "struct DataUnionSidechain.MemberInfo storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "6232:44:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1690, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1686, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1680, + "src": "6294:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1687, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "6294:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1688, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "6309:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1689, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "6309:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "6294:34:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f616c72656164794d656d626572", + "id": 1691, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "6330:21:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_0cd731b7251f7a1d03a588db9c28b82c775f5ea903082a5ec49eb1fc1ac0eb1f", + "typeString": "literal_string \"error_alreadyMember\"" + }, + "value": "error_alreadyMember" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_0cd731b7251f7a1d03a588db9c28b82c775f5ea903082a5ec49eb1fc1ac0eb1f", + "typeString": "literal_string \"error_alreadyMember\"" + } + ], + "id": 1685, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "6286:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1692, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6286:66:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1693, + "nodeType": "ExpressionStatement", + "src": "6286:66:4" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1698, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1694, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1680, + "src": "6365:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1695, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "6365:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1696, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "6380:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1697, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Inactive", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "6380:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "6365:36:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 1707, + "nodeType": "IfStatement", + "src": "6362:113:4", + "trueBody": { + "id": 1706, + "nodeType": "Block", + "src": "6402:73:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 1704, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1699, + "name": "inactiveMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1259, + "src": "6416:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "31", + "id": 1702, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "6462:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "argumentTypes": null, + "id": 1700, + "name": "inactiveMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1259, + "src": "6438:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1701, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "6438:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1703, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6438:26:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "6416:48:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1705, + "nodeType": "ExpressionStatement", + "src": "6416:48:4" + } + ] + } + }, + { + "assignments": [ + 1709 + ], + "declarations": [ + { + "constant": false, + "id": 1709, + "mutability": "mutable", + "name": "sendEth", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1765, + "src": "6484:12:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 1708, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "6484:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1727, + "initialValue": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 1726, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 1718, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1714, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1710, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1680, + "src": "6499:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1711, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "6499:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1712, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "6514:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1713, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "None", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "6514:17:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "6499:32:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "&&", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1717, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1715, + "name": "newMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1265, + "src": "6535:12:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "!=", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 1716, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "6551:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "6535:17:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "6499:53:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "&&", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1725, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1721, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "6564:4:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + ], + "id": 1720, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "6556:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1719, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "6556:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1722, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6556:13:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "id": 1723, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "balance", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "6556:21:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">=", + "rightExpression": { + "argumentTypes": null, + "id": 1724, + "name": "newMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1265, + "src": "6581:12:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "6556:37:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "6499:94:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "6484:109:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1733, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1728, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1680, + "src": "6603:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1730, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "6603:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1731, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "6617:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1732, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "6617:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "6603:33:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "id": 1734, + "nodeType": "ExpressionStatement", + "src": "6603:33:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1739, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1735, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1680, + "src": "6646:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1737, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "lmeAtJoin", + "nodeType": "MemberAccess", + "referencedDeclaration": 1242, + "src": "6646:14:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "id": 1738, + "name": "lifetimeMemberEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1261, + "src": "6663:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "6646:39:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1740, + "nodeType": "ExpressionStatement", + "src": "6646:39:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1746, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1741, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "6695:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "31", + "id": 1744, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "6737:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "argumentTypes": null, + "id": 1742, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "6715:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1743, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "6715:21:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1745, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6715:24:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "6695:44:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1747, + "nodeType": "ExpressionStatement", + "src": "6695:44:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1749, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1674, + "src": "6767:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "id": 1748, + "name": "MemberJoined", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1184, + "src": "6754:12:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 1750, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6754:20:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1751, + "nodeType": "EmitStatement", + "src": "6749:25:4" + }, + { + "condition": { + "argumentTypes": null, + "id": 1752, + "name": "sendEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1709, + "src": "6854:7:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 1764, + "nodeType": "IfStatement", + "src": "6850:131:4", + "trueBody": { + "id": 1763, + "nodeType": "Block", + "src": "6863:118:4", + "statements": [ + { + "condition": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1755, + "name": "newMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1265, + "src": "6893:12:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1753, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1674, + "src": "6881:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "id": 1754, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "send", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "6881:11:4", + "typeDescriptions": { + "typeIdentifier": "t_function_send_nonpayable$_t_uint256_$returns$_t_bool_$", + "typeString": "function (uint256) returns (bool)" + } + }, + "id": 1756, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6881:25:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 1762, + "nodeType": "IfStatement", + "src": "6877:94:4", + "trueBody": { + "id": 1761, + "nodeType": "Block", + "src": "6908:63:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1758, + "name": "newMemberEth", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1265, + "src": "6943:12:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1757, + "name": "NewMemberEthSent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1236, + "src": "6926:16:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_uint256_$returns$__$", + "typeString": "function (uint256)" + } + }, + "id": 1759, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "6926:30:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1760, + "nodeType": "ExpressionStatement", + "src": "6926:30:4" + } + ] + } + } + ] + } + } + ] + }, + "documentation": null, + "functionSelector": "ca6d56dc", + "id": 1766, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 1677, + "modifierName": { + "argumentTypes": null, + "id": 1676, + "name": "onlyJoinPartAgent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1288, + "src": "6204:17:4", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "6204:17:4" + } + ], + "name": "addMember", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1675, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1674, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1766, + "src": "6173:22:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + "typeName": { + "id": 1673, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "6173:15:4", + "stateMutability": "payable", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "6172:24:4" + }, + "returnParameters": { + "id": 1678, + "nodeType": "ParameterList", + "parameters": [], + "src": "6222:0:4" + }, + "scope": 2432, + "src": "6154:833:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1835, + "nodeType": "Block", + "src": "7036:504:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 1783, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 1775, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1772, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "7054:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1773, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "7054:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 1774, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1768, + "src": "7068:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "7054:20:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "||", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1782, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1776, + "name": "joinPartAgents", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1273, + "src": "7078:14:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_enum$_ActiveStatus_$1180_$", + "typeString": "mapping(address => enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1779, + "indexExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1777, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "7093:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1778, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "7093:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "7078:26:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1780, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "7108:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1781, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "7108:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "7078:49:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "7054:73:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6e6f745065726d6974746564", + "id": 1784, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7129:20:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_81fcea6533090d67b5d2d1910c87832a3a952f4bfc10eedbdaba5b028593bde1", + "typeString": "literal_string \"error_notPermitted\"" + }, + "value": "error_notPermitted" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_81fcea6533090d67b5d2d1910c87832a3a952f4bfc10eedbdaba5b028593bde1", + "typeString": "literal_string \"error_notPermitted\"" + } + ], + "id": 1771, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "7046:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1785, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7046:104:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1786, + "nodeType": "ExpressionStatement", + "src": "7046:104:4" + }, + { + "assignments": [ + 1788 + ], + "declarations": [ + { + "constant": false, + "id": 1788, + "mutability": "mutable", + "name": "info", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1835, + "src": "7160:23:4", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + }, + "typeName": { + "contractScope": null, + "id": 1787, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "7160:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1792, + "initialValue": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1789, + "name": "memberData", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1269, + "src": "7186:10:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo storage ref)" + } + }, + "id": 1791, + "indexExpression": { + "argumentTypes": null, + "id": 1790, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1768, + "src": "7197:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "7186:18:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage", + "typeString": "struct DataUnionSidechain.MemberInfo storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "7160:44:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 1798, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1794, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1788, + "src": "7222:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1795, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "7222:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1796, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "7237:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1797, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Active", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "7237:19:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "7222:34:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6e6f744163746976654d656d626572", + "id": 1799, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7258:23:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_bc4828de8fccb67618bdc2462d405754421b1e879aff6628f11ddb63528355b9", + "typeString": "literal_string \"error_notActiveMember\"" + }, + "value": "error_notActiveMember" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_bc4828de8fccb67618bdc2462d405754421b1e879aff6628f11ddb63528355b9", + "typeString": "literal_string \"error_notActiveMember\"" + } + ], + "id": 1793, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "7214:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1800, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7214:68:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1801, + "nodeType": "ExpressionStatement", + "src": "7214:68:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1808, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1802, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1788, + "src": "7292:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1804, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "earningsBeforeLastJoin", + "nodeType": "MemberAccess", + "referencedDeclaration": 1240, + "src": "7292:27:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1806, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1768, + "src": "7334:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1805, + "name": "getEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1451, + "src": "7322:11:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 1807, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7322:19:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "7292:49:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1809, + "nodeType": "ExpressionStatement", + "src": "7292:49:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1815, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1810, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1788, + "src": "7351:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1812, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "7351:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1813, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "7365:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 1814, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Inactive", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "7365:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "7351:35:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "id": 1816, + "nodeType": "ExpressionStatement", + "src": "7351:35:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1822, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1817, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "7396:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "31", + "id": 1820, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7438:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "argumentTypes": null, + "id": 1818, + "name": "activeMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1257, + "src": "7416:17:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1819, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "7416:21:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1821, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7416:24:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "7396:44:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1823, + "nodeType": "ExpressionStatement", + "src": "7396:44:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1829, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1824, + "name": "inactiveMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1259, + "src": "7450:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "31", + "id": 1827, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7496:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "argumentTypes": null, + "id": 1825, + "name": "inactiveMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1259, + "src": "7472:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1826, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "7472:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1828, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7472:26:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "7450:48:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1830, + "nodeType": "ExpressionStatement", + "src": "7450:48:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1832, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1768, + "src": "7526:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1831, + "name": "MemberParted", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1188, + "src": "7513:12:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 1833, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7513:20:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1834, + "nodeType": "EmitStatement", + "src": "7508:25:4" + } + ] + }, + "documentation": null, + "functionSelector": "4e40ea64", + "id": 1836, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "partMember", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1769, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1768, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1836, + "src": "7013:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1767, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "7013:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "7012:16:4" + }, + "returnParameters": { + "id": 1770, + "nodeType": "ParameterList", + "parameters": [], + "src": "7036:0:4" + }, + "scope": 2432, + "src": "6993:547:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1863, + "nodeType": "Block", + "src": "7625:107:4", + "statements": [ + { + "body": { + "id": 1861, + "nodeType": "Block", + "src": "7680:46:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1856, + "name": "members", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1839, + "src": "7704:7:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_payable_$dyn_memory_ptr", + "typeString": "address payable[] memory" + } + }, + "id": 1858, + "indexExpression": { + "argumentTypes": null, + "id": 1857, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1845, + "src": "7712:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "7704:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "id": 1855, + "name": "addMember", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1766, + "src": "7694:9:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_payable_$returns$__$", + "typeString": "function (address payable)" + } + }, + "id": 1859, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7694:21:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1860, + "nodeType": "ExpressionStatement", + "src": "7694:21:4" + } + ] + }, + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1851, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1848, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1845, + "src": "7655:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "<", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1849, + "name": "members", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1839, + "src": "7659:7:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_payable_$dyn_memory_ptr", + "typeString": "address payable[] memory" + } + }, + "id": 1850, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "length", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "7659:14:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "7655:18:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 1862, + "initializationExpression": { + "assignments": [ + 1845 + ], + "declarations": [ + { + "constant": false, + "id": 1845, + "mutability": "mutable", + "name": "i", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1862, + "src": "7640:9:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1844, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "7640:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1847, + "initialValue": { + "argumentTypes": null, + "hexValue": "30", + "id": 1846, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7652:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "nodeType": "VariableDeclarationStatement", + "src": "7640:13:4" + }, + "loopExpression": { + "expression": { + "argumentTypes": null, + "id": 1853, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "++", + "prefix": false, + "src": "7675:3:4", + "subExpression": { + "argumentTypes": null, + "id": 1852, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1845, + "src": "7675:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1854, + "nodeType": "ExpressionStatement", + "src": "7675:3:4" + }, + "nodeType": "ForStatement", + "src": "7635:91:4" + } + ] + }, + "documentation": null, + "functionSelector": "6f4d469b", + "id": 1864, + "implemented": true, + "kind": "function", + "modifiers": [ + { + "arguments": null, + "id": 1842, + "modifierName": { + "argumentTypes": null, + "id": 1841, + "name": "onlyJoinPartAgent", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1288, + "src": "7607:17:4", + "typeDescriptions": { + "typeIdentifier": "t_modifier$__$", + "typeString": "modifier ()" + } + }, + "nodeType": "ModifierInvocation", + "src": "7607:17:4" + } + ], + "name": "addMembers", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1840, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1839, + "mutability": "mutable", + "name": "members", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1864, + "src": "7566:32:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_payable_$dyn_memory_ptr", + "typeString": "address payable[]" + }, + "typeName": { + "baseType": { + "id": 1837, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "7566:15:4", + "stateMutability": "payable", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "id": 1838, + "length": null, + "nodeType": "ArrayTypeName", + "src": "7566:17:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_payable_$dyn_storage_ptr", + "typeString": "address payable[]" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "7565:34:4" + }, + "returnParameters": { + "id": 1843, + "nodeType": "ParameterList", + "parameters": [], + "src": "7625:0:4" + }, + "scope": 2432, + "src": "7546:186:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1889, + "nodeType": "Block", + "src": "7827:108:4", + "statements": [ + { + "body": { + "id": 1887, + "nodeType": "Block", + "src": "7882:47:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1882, + "name": "members", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1867, + "src": "7907:7:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + }, + "id": 1884, + "indexExpression": { + "argumentTypes": null, + "id": 1883, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1871, + "src": "7915:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "7907:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 1881, + "name": "partMember", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1836, + "src": "7896:10:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$returns$__$", + "typeString": "function (address)" + } + }, + "id": 1885, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "7896:22:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1886, + "nodeType": "ExpressionStatement", + "src": "7896:22:4" + } + ] + }, + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1877, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 1874, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1871, + "src": "7857:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "<", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1875, + "name": "members", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1867, + "src": "7861:7:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + }, + "id": 1876, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "length", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "7861:14:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "7857:18:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 1888, + "initializationExpression": { + "assignments": [ + 1871 + ], + "declarations": [ + { + "constant": false, + "id": 1871, + "mutability": "mutable", + "name": "i", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1888, + "src": "7842:9:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1870, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "7842:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1873, + "initialValue": { + "argumentTypes": null, + "hexValue": "30", + "id": 1872, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "7854:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "nodeType": "VariableDeclarationStatement", + "src": "7842:13:4" + }, + "loopExpression": { + "expression": { + "argumentTypes": null, + "id": 1879, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "++", + "prefix": false, + "src": "7877:3:4", + "subExpression": { + "argumentTypes": null, + "id": 1878, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1871, + "src": "7877:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1880, + "nodeType": "ExpressionStatement", + "src": "7877:3:4" + }, + "nodeType": "ForStatement", + "src": "7837:92:4" + } + ] + }, + "documentation": null, + "functionSelector": "7b30ed43", + "id": 1890, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "partMembers", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1868, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1867, + "mutability": "mutable", + "name": "members", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1890, + "src": "7794:24:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 1865, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "7794:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 1866, + "length": null, + "nodeType": "ArrayTypeName", + "src": "7794:9:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "7793:26:4" + }, + "returnParameters": { + "id": 1869, + "nodeType": "ParameterList", + "parameters": [], + "src": "7827:0:4" + }, + "scope": 2432, + "src": "7773:162:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 1961, + "nodeType": "Block", + "src": "8119:455:4", + "statements": [ + { + "assignments": [ + 1899 + ], + "declarations": [ + { + "constant": false, + "id": 1899, + "mutability": "mutable", + "name": "bal_before", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1961, + "src": "8129:15:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1898, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "8129:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1907, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1904, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "8171:4:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + ], + "id": 1903, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "8163:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1902, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "8163:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1905, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8163:13:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "expression": { + "argumentTypes": null, + "id": 1900, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "8147:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "id": 1901, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "balanceOf", + "nodeType": "MemberAccess", + "referencedDeclaration": 4221, + "src": "8147:15:4", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view external returns (uint256)" + } + }, + "id": 1906, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8147:30:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "8129:48:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1911, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "8214:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1912, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "8214:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1915, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "8234:4:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + ], + "id": 1914, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "8226:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1913, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "8226:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1916, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8226:13:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 1917, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1895, + "src": "8241:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1909, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "8195:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "id": 1910, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "transferFrom", + "nodeType": "MemberAccess", + "referencedDeclaration": 4263, + "src": "8195:18:4", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$_t_bool_$", + "typeString": "function (address,address,uint256) external returns (bool)" + } + }, + "id": 1918, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8195:53:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f7472616e73666572", + "id": 1919, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "8250:16:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + }, + "value": "error_transfer" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + } + ], + "id": 1908, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "8187:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1920, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8187:80:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1921, + "nodeType": "ExpressionStatement", + "src": "8187:80:4" + }, + { + "assignments": [ + 1923 + ], + "declarations": [ + { + "constant": false, + "id": 1923, + "mutability": "mutable", + "name": "bal_after", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1961, + "src": "8277:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1922, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "8277:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1931, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1928, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "8318:4:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + ], + "id": 1927, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "8310:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 1926, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "8310:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 1929, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8310:13:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "expression": { + "argumentTypes": null, + "id": 1924, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "8294:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "id": 1925, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "balanceOf", + "nodeType": "MemberAccess", + "referencedDeclaration": 4221, + "src": "8294:15:4", + "typeDescriptions": { + "typeIdentifier": "t_function_external_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view external returns (uint256)" + } + }, + "id": 1930, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8294:30:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "8277:47:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1938, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1935, + "name": "bal_before", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1899, + "src": "8356:10:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1933, + "name": "bal_after", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1923, + "src": "8342:9:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1934, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sub", + "nodeType": "MemberAccess", + "referencedDeclaration": 3491, + "src": "8342:13:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1936, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8342:25:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">=", + "rightExpression": { + "argumentTypes": null, + "id": 1937, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1895, + "src": "8371:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "8342:35:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f7472616e73666572", + "id": 1939, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "8379:16:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + }, + "value": "error_transfer" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + } + ], + "id": 1932, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "8334:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1940, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8334:62:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1941, + "nodeType": "ExpressionStatement", + "src": "8334:62:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1943, + "name": "recipient", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1893, + "src": "8424:9:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1944, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1895, + "src": "8435:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1942, + "name": "_increaseBalance", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2056, + "src": "8407:16:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 1945, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8407:35:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1946, + "nodeType": "ExpressionStatement", + "src": "8407:35:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1952, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 1947, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1253, + "src": "8452:13:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1950, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1895, + "src": "8486:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 1948, + "name": "totalEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1253, + "src": "8468:13:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1949, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "8468:17:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1951, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8468:25:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "8452:41:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1953, + "nodeType": "ExpressionStatement", + "src": "8452:41:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1955, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "8536:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1956, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "8536:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 1957, + "name": "recipient", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1893, + "src": "8548:9:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1958, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1895, + "src": "8560:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1954, + "name": "TransferToAddressInContract", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1228, + "src": "8508:27:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,address,uint256)" + } + }, + "id": 1959, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "8508:59:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1960, + "nodeType": "EmitStatement", + "src": "8503:64:4" + } + ] + }, + "documentation": { + "id": 1891, + "nodeType": "StructuredDocumentation", + "src": "7941:98:4", + "text": "Transfer tokens from outside contract, add to a recipient's in-contract balance" + }, + "functionSelector": "b274bcc7", + "id": 1962, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "transferToMemberInContract", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1896, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1893, + "mutability": "mutable", + "name": "recipient", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1962, + "src": "8080:17:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1892, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "8080:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1895, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 1962, + "src": "8099:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1894, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "8099:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "8079:32:4" + }, + "returnParameters": { + "id": 1897, + "nodeType": "ParameterList", + "parameters": [], + "src": "8119:0:4" + }, + "scope": 2432, + "src": "8044:530:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2009, + "nodeType": "Block", + "src": "9083:399:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 1976, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1972, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "9125:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1973, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "9125:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "id": 1971, + "name": "getWithdrawableEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1494, + "src": "9101:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 1974, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "9101:35:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": ">=", + "rightExpression": { + "argumentTypes": null, + "id": 1975, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1967, + "src": "9140:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "9101:45:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f696e73756666696369656e7442616c616e6365", + "id": 1977, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9148:27:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_74799d65376d943bac023262731d6421d2e6b6332748a55385eb85a0683327fc", + "typeString": "literal_string \"error_insufficientBalance\"" + }, + "value": "error_insufficientBalance" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_74799d65376d943bac023262731d6421d2e6b6332748a55385eb85a0683327fc", + "typeString": "literal_string \"error_insufficientBalance\"" + } + ], + "id": 1970, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "9093:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 1978, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "9093:83:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 1979, + "nodeType": "ExpressionStatement", + "src": "9093:83:4" + }, + { + "assignments": [ + 1981 + ], + "declarations": [ + { + "constant": false, + "id": 1981, + "mutability": "mutable", + "name": "info", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2009, + "src": "9245:23:4", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + }, + "typeName": { + "contractScope": null, + "id": 1980, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "9245:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 1986, + "initialValue": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 1982, + "name": "memberData", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1269, + "src": "9271:10:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo storage ref)" + } + }, + "id": 1985, + "indexExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1983, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "9282:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 1984, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "9282:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "9271:22:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage", + "typeString": "struct DataUnionSidechain.MemberInfo storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "9245:48:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 1995, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1987, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1981, + "src": "9303:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1989, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "withdrawnEarnings", + "nodeType": "MemberAccess", + "referencedDeclaration": 1244, + "src": "9303:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1993, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1967, + "src": "9355:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 1990, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1981, + "src": "9328:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 1991, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "withdrawnEarnings", + "nodeType": "MemberAccess", + "referencedDeclaration": 1244, + "src": "9328:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1992, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "9328:26:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 1994, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "9328:34:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "9303:59:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 1996, + "nodeType": "ExpressionStatement", + "src": "9303:59:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 1998, + "name": "recipient", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1965, + "src": "9389:9:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 1999, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1967, + "src": "9400:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 1997, + "name": "_increaseBalance", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2056, + "src": "9372:16:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 2000, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "9372:35:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2001, + "nodeType": "ExpressionStatement", + "src": "9372:35:4" + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2003, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "9445:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 2004, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "9445:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 2005, + "name": "recipient", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1965, + "src": "9457:9:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2006, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1967, + "src": "9468:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 2002, + "name": "TransferWithinContract", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1220, + "src": "9422:22:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,address,uint256)" + } + }, + "id": 2007, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "9422:53:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2008, + "nodeType": "EmitStatement", + "src": "9417:58:4" + } + ] + }, + "documentation": { + "id": 1963, + "nodeType": "StructuredDocumentation", + "src": "8580:427:4", + "text": "Transfer tokens from sender's in-contract balance to recipient's in-contract balance\nThis is done by \"withdrawing\" sender's earnings and crediting them to recipient's unwithdrawn earnings,\n so withdrawnEarnings never decreases for anyone (within this function)\n@param recipient whose withdrawable earnings will increase\n@param amount how much withdrawable earnings is transferred" + }, + "functionSelector": "71cdfd68", + "id": 2010, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "transferWithinContract", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 1968, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 1965, + "mutability": "mutable", + "name": "recipient", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2010, + "src": "9044:17:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 1964, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "9044:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 1967, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2010, + "src": "9063:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 1966, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "9063:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "9043:32:4" + }, + "returnParameters": { + "id": 1969, + "nodeType": "ParameterList", + "parameters": [], + "src": "9083:0:4" + }, + "scope": 2432, + "src": "9012:470:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2055, + "nodeType": "Block", + "src": "9642:359:4", + "statements": [ + { + "assignments": [ + 2019 + ], + "declarations": [ + { + "constant": false, + "id": 2019, + "mutability": "mutable", + "name": "info", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2055, + "src": "9652:23:4", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + }, + "typeName": { + "contractScope": null, + "id": 2018, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "9652:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2023, + "initialValue": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 2020, + "name": "memberData", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1269, + "src": "9678:10:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo storage ref)" + } + }, + "id": 2022, + "indexExpression": { + "argumentTypes": null, + "id": 2021, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2013, + "src": "9689:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "9678:18:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage", + "typeString": "struct DataUnionSidechain.MemberInfo storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "9652:44:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 2032, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2024, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2019, + "src": "9706:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 2026, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "earningsBeforeLastJoin", + "nodeType": "MemberAccess", + "referencedDeclaration": 1240, + "src": "9706:27:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2030, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2015, + "src": "9768:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2027, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2019, + "src": "9736:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 2028, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "earningsBeforeLastJoin", + "nodeType": "MemberAccess", + "referencedDeclaration": 1240, + "src": "9736:27:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2029, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "9736:31:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 2031, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "9736:39:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "9706:69:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2033, + "nodeType": "ExpressionStatement", + "src": "9706:69:4" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + }, + "id": 2038, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2034, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2019, + "src": "9839:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 2035, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "9839:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2036, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "9854:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 2037, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "None", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "9854:17:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "9839:32:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 2054, + "nodeType": "IfStatement", + "src": "9835:160:4", + "trueBody": { + "id": 2053, + "nodeType": "Block", + "src": "9873:122:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 2044, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2039, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2019, + "src": "9887:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 2041, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "status", + "nodeType": "MemberAccess", + "referencedDeclaration": 1238, + "src": "9887:11:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2042, + "name": "ActiveStatus", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1180, + "src": "9901:12:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_enum$_ActiveStatus_$1180_$", + "typeString": "type(enum DataUnionSidechain.ActiveStatus)" + } + }, + "id": 2043, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "Inactive", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "9901:21:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "src": "9887:35:4", + "typeDescriptions": { + "typeIdentifier": "t_enum$_ActiveStatus_$1180", + "typeString": "enum DataUnionSidechain.ActiveStatus" + } + }, + "id": 2045, + "nodeType": "ExpressionStatement", + "src": "9887:35:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 2051, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 2046, + "name": "inactiveMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1259, + "src": "9936:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "31", + "id": 2049, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "9982:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + }, + "value": "1" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_rational_1_by_1", + "typeString": "int_const 1" + } + ], + "expression": { + "argumentTypes": null, + "id": 2047, + "name": "inactiveMemberCount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1259, + "src": "9958:19:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2048, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "9958:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 2050, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "9958:26:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "9936:48:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2052, + "nodeType": "ExpressionStatement", + "src": "9936:48:4" + } + ] + } + } + ] + }, + "documentation": { + "id": 2011, + "nodeType": "StructuredDocumentation", + "src": "9488:85:4", + "text": "Hack to add to single member's balance without affecting lmeAtJoin" + }, + "id": 2056, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "_increaseBalance", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2016, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2013, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2056, + "src": "9604:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2012, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "9604:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2015, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2056, + "src": "9620:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2014, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "9620:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "9603:29:4" + }, + "returnParameters": { + "id": 2017, + "nodeType": "ParameterList", + "parameters": [], + "src": "9642:0:4" + }, + "scope": 2432, + "src": "9578:423:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "internal" + }, + { + "body": { + "id": 2097, + "nodeType": "Block", + "src": "10123:208:4", + "statements": [ + { + "assignments": [ + 2067 + ], + "declarations": [ + { + "constant": false, + "id": 2067, + "mutability": "mutable", + "name": "withdrawn", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2097, + "src": "10133:17:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2066, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "10133:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2069, + "initialValue": { + "argumentTypes": null, + "hexValue": "30", + "id": 2068, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "10153:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "nodeType": "VariableDeclarationStatement", + "src": "10133:21:4" + }, + { + "body": { + "id": 2093, + "nodeType": "Block", + "src": "10209:90:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 2091, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 2081, + "name": "withdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2067, + "src": "10223:9:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 2085, + "name": "members", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2059, + "src": "10261:7:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + }, + "id": 2087, + "indexExpression": { + "argumentTypes": null, + "id": 2086, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2071, + "src": "10269:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "10261:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2088, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2061, + "src": "10273:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 2084, + "name": "withdrawAll", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2116, + "src": "10249:11:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_bool_$returns$_t_uint256_$", + "typeString": "function (address,bool) returns (uint256)" + } + }, + "id": 2089, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10249:38:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 2082, + "name": "withdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2067, + "src": "10235:9:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2083, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "10235:13:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 2090, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10235:53:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "10223:65:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2092, + "nodeType": "ExpressionStatement", + "src": "10223:65:4" + } + ] + }, + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 2077, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 2074, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2071, + "src": "10184:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "<", + "rightExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2075, + "name": "members", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2059, + "src": "10188:7:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[] memory" + } + }, + "id": 2076, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "length", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "10188:14:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "10184:18:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "id": 2094, + "initializationExpression": { + "assignments": [ + 2071 + ], + "declarations": [ + { + "constant": false, + "id": 2071, + "mutability": "mutable", + "name": "i", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2094, + "src": "10169:9:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2070, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "10169:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2073, + "initialValue": { + "argumentTypes": null, + "hexValue": "30", + "id": 2072, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "10181:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "nodeType": "VariableDeclarationStatement", + "src": "10169:13:4" + }, + "loopExpression": { + "expression": { + "argumentTypes": null, + "id": 2079, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "nodeType": "UnaryOperation", + "operator": "++", + "prefix": false, + "src": "10204:3:4", + "subExpression": { + "argumentTypes": null, + "id": 2078, + "name": "i", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2071, + "src": "10204:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2080, + "nodeType": "ExpressionStatement", + "src": "10204:3:4" + }, + "nodeType": "ForStatement", + "src": "10164:135:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 2095, + "name": "withdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2067, + "src": "10315:9:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2065, + "id": 2096, + "nodeType": "Return", + "src": "10308:16:4" + } + ] + }, + "documentation": null, + "functionSelector": "a4d6ddc0", + "id": 2098, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdrawMembers", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2062, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2059, + "mutability": "mutable", + "name": "members", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2098, + "src": "10032:24:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_memory_ptr", + "typeString": "address[]" + }, + "typeName": { + "baseType": { + "id": 2057, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "10032:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "id": 2058, + "length": null, + "nodeType": "ArrayTypeName", + "src": "10032:9:4", + "typeDescriptions": { + "typeIdentifier": "t_array$_t_address_$dyn_storage_ptr", + "typeString": "address[]" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2061, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2098, + "src": "10058:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2060, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "10058:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10031:46:4" + }, + "returnParameters": { + "id": 2065, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2064, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2098, + "src": "10110:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2063, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "10110:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10109:9:4" + }, + "scope": 2432, + "src": "10007:324:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2115, + "nodeType": "Block", + "src": "10439:88:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2108, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2100, + "src": "10465:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2110, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2100, + "src": "10497:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 2109, + "name": "getWithdrawableEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1494, + "src": "10473:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 2111, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10473:31:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 2112, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2102, + "src": "10506:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 2107, + "name": "withdraw", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2148, + "src": "10456:8:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$_t_bool_$returns$_t_uint256_$", + "typeString": "function (address,uint256,bool) returns (uint256)" + } + }, + "id": 2113, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10456:64:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2106, + "id": 2114, + "nodeType": "Return", + "src": "10449:71:4" + } + ] + }, + "documentation": null, + "functionSelector": "4bee9137", + "id": 2116, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdrawAll", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2103, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2100, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2116, + "src": "10358:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2099, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "10358:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2102, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2116, + "src": "10374:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2101, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "10374:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10357:36:4" + }, + "returnParameters": { + "id": 2106, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2105, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2116, + "src": "10426:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2104, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "10426:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10425:9:4" + }, + "scope": 2432, + "src": "10337:190:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2147, + "nodeType": "Block", + "src": "10645:156:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 2136, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 2131, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2128, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "10663:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 2129, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "10663:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 2130, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2118, + "src": "10677:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "10663:20:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "||", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 2135, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2132, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "10687:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 2133, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "10687:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 2134, + "name": "owner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 3105, + "src": "10701:5:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "10687:19:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "10663:43:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6e6f745065726d6974746564", + "id": 2137, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "10708:20:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_81fcea6533090d67b5d2d1910c87832a3a952f4bfc10eedbdaba5b028593bde1", + "typeString": "literal_string \"error_notPermitted\"" + }, + "value": "error_notPermitted" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_81fcea6533090d67b5d2d1910c87832a3a952f4bfc10eedbdaba5b028593bde1", + "typeString": "literal_string \"error_notPermitted\"" + } + ], + "id": 2127, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "10655:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2138, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10655:74:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2139, + "nodeType": "ExpressionStatement", + "src": "10655:74:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2141, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2118, + "src": "10756:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2142, + "name": "member", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2118, + "src": "10764:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2143, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2120, + "src": "10772:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 2144, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2122, + "src": "10780:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 2140, + "name": "_withdraw", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2431, + "src": "10746:9:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_address_$_t_uint256_$_t_bool_$returns$_t_uint256_$", + "typeString": "function (address,address,uint256,bool) returns (uint256)" + } + }, + "id": 2145, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10746:48:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2126, + "id": 2146, + "nodeType": "Return", + "src": "10739:55:4" + } + ] + }, + "documentation": null, + "functionSelector": "ead5d359", + "id": 2148, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdraw", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2123, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2118, + "mutability": "mutable", + "name": "member", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2148, + "src": "10551:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2117, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "10551:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2120, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2148, + "src": "10567:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2119, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "10567:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2122, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2148, + "src": "10580:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2121, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "10580:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10550:49:4" + }, + "returnParameters": { + "id": 2126, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2125, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2148, + "src": "10632:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2124, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "10632:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10631:9:4" + }, + "scope": 2432, + "src": "10533:268:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2166, + "nodeType": "Block", + "src": "10907:90:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2158, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2150, + "src": "10935:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2160, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "10963:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 2161, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "10963:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + ], + "id": 2159, + "name": "getWithdrawableEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1494, + "src": "10939:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 2162, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10939:35:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 2163, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2152, + "src": "10976:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 2157, + "name": "withdrawTo", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2187, + "src": "10924:10:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_uint256_$_t_bool_$returns$_t_uint256_$", + "typeString": "function (address,uint256,bool) returns (uint256)" + } + }, + "id": 2164, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "10924:66:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2156, + "id": 2165, + "nodeType": "Return", + "src": "10917:73:4" + } + ] + }, + "documentation": null, + "functionSelector": "2b94411f", + "id": 2167, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdrawAllTo", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2153, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2150, + "mutability": "mutable", + "name": "to", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2167, + "src": "10830:10:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2149, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "10830:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2152, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2167, + "src": "10842:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2151, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "10842:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10829:32:4" + }, + "returnParameters": { + "id": 2156, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2155, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2167, + "src": "10894:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2154, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "10894:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "10893:9:4" + }, + "scope": 2432, + "src": "10807:190:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2186, + "nodeType": "Block", + "src": "11113:72:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2179, + "name": "msg", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -15, + "src": "11140:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_message", + "typeString": "msg" + } + }, + "id": 2180, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "sender", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "11140:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "id": 2181, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2169, + "src": "11152:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2182, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2171, + "src": "11156:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 2183, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2173, + "src": "11164:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 2178, + "name": "_withdraw", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2431, + "src": "11130:9:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_address_$_t_uint256_$_t_bool_$returns$_t_uint256_$", + "typeString": "function (address,address,uint256,bool) returns (uint256)" + } + }, + "id": 2184, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "11130:48:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2177, + "id": 2185, + "nodeType": "Return", + "src": "11123:55:4" + } + ] + }, + "documentation": null, + "functionSelector": "73e2290c", + "id": 2187, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdrawTo", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2174, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2169, + "mutability": "mutable", + "name": "to", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2187, + "src": "11023:10:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2168, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "11023:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2171, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2187, + "src": "11035:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2170, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "11035:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2173, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2187, + "src": "11048:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2172, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "11048:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "11022:45:4" + }, + "returnParameters": { + "id": 2177, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2176, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2187, + "src": "11100:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2175, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "11100:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "11099:9:4" + }, + "scope": 2432, + "src": "11003:182:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2270, + "nodeType": "Block", + "src": "12594:831:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 2205, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2202, + "name": "signature", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2196, + "src": "12612:9:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + }, + "id": 2203, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "length", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "12612:16:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "3635", + "id": 2204, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "12632:2:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_65_by_1", + "typeString": "int_const 65" + }, + "value": "65" + }, + "src": "12612:22:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6261645369676e61747572654c656e677468", + "id": 2206, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "12636:26:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_4077da77d01843e066cb3ce202dcb5c333f11623972b2a584316e75b72c622e0", + "typeString": "literal_string \"error_badSignatureLength\"" + }, + "value": "error_badSignatureLength" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_4077da77d01843e066cb3ce202dcb5c333f11623972b2a584316e75b72c622e0", + "typeString": "literal_string \"error_badSignatureLength\"" + } + ], + "id": 2201, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "12604:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2207, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "12604:59:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2208, + "nodeType": "ExpressionStatement", + "src": "12604:59:4" + }, + { + "assignments": [ + 2210 + ], + "declarations": [ + { + "constant": false, + "id": 2210, + "mutability": "mutable", + "name": "r", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2270, + "src": "12674:9:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 2209, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "12674:7:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2211, + "initialValue": null, + "nodeType": "VariableDeclarationStatement", + "src": "12674:9:4" + }, + { + "assignments": [ + 2213 + ], + "declarations": [ + { + "constant": false, + "id": 2213, + "mutability": "mutable", + "name": "s", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2270, + "src": "12685:9:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 2212, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "12685:7:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2214, + "initialValue": null, + "nodeType": "VariableDeclarationStatement", + "src": "12685:9:4" + }, + { + "assignments": [ + 2216 + ], + "declarations": [ + { + "constant": false, + "id": 2216, + "mutability": "mutable", + "name": "v", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2270, + "src": "12696:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "typeName": { + "id": 2215, + "name": "uint8", + "nodeType": "ElementaryTypeName", + "src": "12696:5:4", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2217, + "initialValue": null, + "nodeType": "VariableDeclarationStatement", + "src": "12696:7:4" + }, + { + "AST": { + "nodeType": "YulBlock", + "src": "12722:205:4", + "statements": [ + { + "nodeType": "YulAssignment", + "src": "12792:30:4", + "value": { + "arguments": [ + { + "arguments": [ + { + "name": "signature", + "nodeType": "YulIdentifier", + "src": "12807:9:4" + }, + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "12818:2:4", + "type": "", + "value": "32" + } + ], + "functionName": { + "name": "add", + "nodeType": "YulIdentifier", + "src": "12803:3:4" + }, + "nodeType": "YulFunctionCall", + "src": "12803:18:4" + } + ], + "functionName": { + "name": "mload", + "nodeType": "YulIdentifier", + "src": "12797:5:4" + }, + "nodeType": "YulFunctionCall", + "src": "12797:25:4" + }, + "variableNames": [ + { + "name": "r", + "nodeType": "YulIdentifier", + "src": "12792:1:4" + } + ] + }, + { + "nodeType": "YulAssignment", + "src": "12835:30:4", + "value": { + "arguments": [ + { + "arguments": [ + { + "name": "signature", + "nodeType": "YulIdentifier", + "src": "12850:9:4" + }, + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "12861:2:4", + "type": "", + "value": "64" + } + ], + "functionName": { + "name": "add", + "nodeType": "YulIdentifier", + "src": "12846:3:4" + }, + "nodeType": "YulFunctionCall", + "src": "12846:18:4" + } + ], + "functionName": { + "name": "mload", + "nodeType": "YulIdentifier", + "src": "12840:5:4" + }, + "nodeType": "YulFunctionCall", + "src": "12840:25:4" + }, + "variableNames": [ + { + "name": "s", + "nodeType": "YulIdentifier", + "src": "12835:1:4" + } + ] + }, + { + "nodeType": "YulAssignment", + "src": "12878:39:4", + "value": { + "arguments": [ + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "12888:1:4", + "type": "", + "value": "0" + }, + { + "arguments": [ + { + "arguments": [ + { + "name": "signature", + "nodeType": "YulIdentifier", + "src": "12901:9:4" + }, + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "12912:2:4", + "type": "", + "value": "96" + } + ], + "functionName": { + "name": "add", + "nodeType": "YulIdentifier", + "src": "12897:3:4" + }, + "nodeType": "YulFunctionCall", + "src": "12897:18:4" + } + ], + "functionName": { + "name": "mload", + "nodeType": "YulIdentifier", + "src": "12891:5:4" + }, + "nodeType": "YulFunctionCall", + "src": "12891:25:4" + } + ], + "functionName": { + "name": "byte", + "nodeType": "YulIdentifier", + "src": "12883:4:4" + }, + "nodeType": "YulFunctionCall", + "src": "12883:34:4" + }, + "variableNames": [ + { + "name": "v", + "nodeType": "YulIdentifier", + "src": "12878:1:4" + } + ] + } + ] + }, + "evmVersion": "istanbul", + "externalReferences": [ + { + "declaration": 2210, + "isOffset": false, + "isSlot": false, + "src": "12792:1:4", + "valueSize": 1 + }, + { + "declaration": 2213, + "isOffset": false, + "isSlot": false, + "src": "12835:1:4", + "valueSize": 1 + }, + { + "declaration": 2196, + "isOffset": false, + "isSlot": false, + "src": "12807:9:4", + "valueSize": 1 + }, + { + "declaration": 2196, + "isOffset": false, + "isSlot": false, + "src": "12850:9:4", + "valueSize": 1 + }, + { + "declaration": 2196, + "isOffset": false, + "isSlot": false, + "src": "12901:9:4", + "valueSize": 1 + }, + { + "declaration": 2216, + "isOffset": false, + "isSlot": false, + "src": "12878:1:4", + "valueSize": 1 + } + ], + "id": 2218, + "nodeType": "InlineAssembly", + "src": "12713:214:4" + }, + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "id": 2221, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 2219, + "name": "v", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2216, + "src": "12940:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "BinaryOperation", + "operator": "<", + "rightExpression": { + "argumentTypes": null, + "hexValue": "3237", + "id": 2220, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "12944:2:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_27_by_1", + "typeString": "int_const 27" + }, + "value": "27" + }, + "src": "12940:6:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 2227, + "nodeType": "IfStatement", + "src": "12936:44:4", + "trueBody": { + "id": 2226, + "nodeType": "Block", + "src": "12948:32:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "id": 2224, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 2222, + "name": "v", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2216, + "src": "12962:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "Assignment", + "operator": "+=", + "rightHandSide": { + "argumentTypes": null, + "hexValue": "3237", + "id": 2223, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "12967:2:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_27_by_1", + "typeString": "int_const 27" + }, + "value": "27" + }, + "src": "12962:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "id": 2225, + "nodeType": "ExpressionStatement", + "src": "12962:7:4" + } + ] + } + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "id": 2235, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "id": 2231, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 2229, + "name": "v", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2216, + "src": "12997:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "3237", + "id": 2230, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "13002:2:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_27_by_1", + "typeString": "int_const 27" + }, + "value": "27" + }, + "src": "12997:7:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "nodeType": "BinaryOperation", + "operator": "||", + "rightExpression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + "id": 2234, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 2232, + "name": "v", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2216, + "src": "13008:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "3238", + "id": 2233, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "13013:2:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_28_by_1", + "typeString": "int_const 28" + }, + "value": "28" + }, + "src": "13008:7:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "src": "12997:18:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6261645369676e617475726556657273696f6e", + "id": 2236, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "13017:27:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_8fa0eae5d18986979b1a6399b1aa7a4d8b3c5537b79b5808266bd780a9c464db", + "typeString": "literal_string \"error_badSignatureVersion\"" + }, + "value": "error_badSignatureVersion" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_8fa0eae5d18986979b1a6399b1aa7a4d8b3c5537b79b5808266bd780a9c464db", + "typeString": "literal_string \"error_badSignatureVersion\"" + } + ], + "id": 2228, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "12989:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2237, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "12989:56:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2238, + "nodeType": "ExpressionStatement", + "src": "12989:56:4" + }, + { + "assignments": [ + 2240 + ], + "declarations": [ + { + "constant": false, + "id": 2240, + "mutability": "mutable", + "name": "messageHash", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2270, + "src": "13151:19:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + "typeName": { + "id": 2239, + "name": "bytes32", + "nodeType": "ElementaryTypeName", + "src": "13151:7:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2256, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "hexValue": "19457468657265756d205369676e6564204d6573736167653a0a313034", + "id": 2244, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "13213:35:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_8d960065d1d57999e7234857e6a0652c524bb2aeb2c4d7c6b962ce1fe99d1ca0", + "typeString": "literal_string \"\u0019Ethereum Signed Message:\n104\"" + }, + "value": "\u0019Ethereum Signed Message:\n104" + }, + { + "argumentTypes": null, + "id": 2245, + "name": "recipient", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2192, + "src": "13250:9:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2246, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2194, + "src": "13261:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2249, + "name": "this", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -28, + "src": "13277:4:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_contract$_DataUnionSidechain_$2432", + "typeString": "contract DataUnionSidechain" + } + ], + "id": 2248, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "nodeType": "ElementaryTypeNameExpression", + "src": "13269:7:4", + "typeDescriptions": { + "typeIdentifier": "t_type$_t_address_$", + "typeString": "type(address)" + }, + "typeName": { + "id": 2247, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "13269:7:4", + "typeDescriptions": { + "typeIdentifier": null, + "typeString": null + } + } + }, + "id": 2250, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "typeConversion", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "13269:13:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2252, + "name": "signer", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2190, + "src": "13297:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 2251, + "name": "getWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1477, + "src": "13284:12:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 2253, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "13284:20:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_stringliteral_8d960065d1d57999e7234857e6a0652c524bb2aeb2c4d7c6b962ce1fe99d1ca0", + "typeString": "literal_string \"\u0019Ethereum Signed Message:\n104\"" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_address_payable", + "typeString": "address payable" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 2242, + "name": "abi", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -1, + "src": "13183:3:4", + "typeDescriptions": { + "typeIdentifier": "t_magic_abi", + "typeString": "abi" + } + }, + "id": 2243, + "isConstant": false, + "isLValue": false, + "isPure": true, + "lValueRequested": false, + "memberName": "encodePacked", + "nodeType": "MemberAccess", + "referencedDeclaration": null, + "src": "13183:16:4", + "typeDescriptions": { + "typeIdentifier": "t_function_abiencodepacked_pure$__$returns$_t_bytes_memory_ptr_$", + "typeString": "function () pure returns (bytes memory)" + } + }, + "id": 2254, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "13183:122:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "id": 2241, + "name": "keccak256", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -8, + "src": "13173:9:4", + "typeDescriptions": { + "typeIdentifier": "t_function_keccak256_pure$_t_bytes_memory_ptr_$returns$_t_bytes32_$", + "typeString": "function (bytes memory) pure returns (bytes32)" + } + }, + "id": 2255, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "13173:133:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "13151:155:4" + }, + { + "assignments": [ + 2258 + ], + "declarations": [ + { + "constant": false, + "id": 2258, + "mutability": "mutable", + "name": "calculatedSigner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2270, + "src": "13316:24:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2257, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "13316:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2265, + "initialValue": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2260, + "name": "messageHash", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2240, + "src": "13353:11:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "argumentTypes": null, + "id": 2261, + "name": "v", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2216, + "src": "13366:1:4", + "typeDescriptions": { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + } + }, + { + "argumentTypes": null, + "id": 2262, + "name": "r", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2210, + "src": "13369:1:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + }, + { + "argumentTypes": null, + "id": 2263, + "name": "s", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2213, + "src": "13372:1:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_uint8", + "typeString": "uint8" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + }, + { + "typeIdentifier": "t_bytes32", + "typeString": "bytes32" + } + ], + "id": 2259, + "name": "ecrecover", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": -6, + "src": "13343:9:4", + "typeDescriptions": { + "typeIdentifier": "t_function_ecrecover_pure$_t_bytes32_$_t_uint8_$_t_bytes32_$_t_bytes32_$returns$_t_address_$", + "typeString": "function (bytes32,uint8,bytes32,bytes32) pure returns (address)" + } + }, + "id": 2264, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "13343:31:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "13316:58:4" + }, + { + "expression": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "id": 2268, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 2266, + "name": "calculatedSigner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2258, + "src": "13392:16:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "id": 2267, + "name": "signer", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2190, + "src": "13412:6:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "src": "13392:26:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "functionReturnParameters": 2200, + "id": 2269, + "nodeType": "Return", + "src": "13385:33:4" + } + ] + }, + "documentation": { + "id": 2188, + "nodeType": "StructuredDocumentation", + "src": "11191:1207:4", + "text": "Check signature from a member authorizing withdrawing its earnings to another account.\nThrows if the signature is badly formatted or doesn't match the given signer and amount.\nSignature has parts the act as replay protection:\n1) `address(this)`: signature can't be used for other contracts;\n2) `withdrawn[signer]`: signature only works once (for unspecified amount), and can be \"cancelled\" by sending a withdraw tx.\nGenerated in Javascript with: `web3.eth.accounts.sign(recipientAddress + amount.toString(16, 64) + contractAddress.slice(2) + withdrawnTokens.toString(16, 64), signerPrivateKey)`,\nor for unlimited amount: `web3.eth.accounts.sign(recipientAddress + \"0\".repeat(64) + contractAddress.slice(2) + withdrawnTokens.toString(16, 64), signerPrivateKey)`.\n@param signer whose earnings are being withdrawn\n@param recipient of the tokens\n@param amount how much is authorized for withdraw, or zero for unlimited (withdrawAll)\n@param signature byte array from `web3.eth.accounts.sign`\n@return isValid true iff signer of the authorization (member whose earnings are going to be withdrawn) matches the signature" + }, + "functionSelector": "a2d3cf4b", + "id": 2271, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "signatureIsValid", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2197, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2190, + "mutability": "mutable", + "name": "signer", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2271, + "src": "12438:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2189, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "12438:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2192, + "mutability": "mutable", + "name": "recipient", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2271, + "src": "12462:17:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2191, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "12462:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2194, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2271, + "src": "12489:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2193, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "12489:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2196, + "mutability": "mutable", + "name": "signature", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2271, + "src": "12510:22:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 2195, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "12510:5:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "12428:110:4" + }, + "returnParameters": { + "id": 2200, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2199, + "mutability": "mutable", + "name": "isValid", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2271, + "src": "12576:12:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2198, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "12576:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "12575:14:4" + }, + "scope": 2432, + "src": "12403:1022:4", + "stateMutability": "view", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2304, + "nodeType": "Block", + "src": "14491:188:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2287, + "name": "fromSigner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2274, + "src": "14526:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2288, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2276, + "src": "14538:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "hexValue": "30", + "id": 2289, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "14542:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + { + "argumentTypes": null, + "id": 2290, + "name": "signature", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2280, + "src": "14545:9:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "id": 2286, + "name": "signatureIsValid", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2271, + "src": "14509:16:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$_t_address_$_t_uint256_$_t_bytes_memory_ptr_$returns$_t_bool_$", + "typeString": "function (address,address,uint256,bytes memory) view returns (bool)" + } + }, + "id": 2291, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "14509:46:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6261645369676e6174757265", + "id": 2292, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "14557:20:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_2ec4ea0e2156213061c2a099c127d1a6548cdd3e5b4bf1f695ceebe1aeb9d2b2", + "typeString": "literal_string \"error_badSignature\"" + }, + "value": "error_badSignature" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_2ec4ea0e2156213061c2a099c127d1a6548cdd3e5b4bf1f695ceebe1aeb9d2b2", + "typeString": "literal_string \"error_badSignature\"" + } + ], + "id": 2285, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "14501:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2293, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "14501:77:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2294, + "nodeType": "ExpressionStatement", + "src": "14501:77:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2296, + "name": "fromSigner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2274, + "src": "14605:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2297, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2276, + "src": "14617:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2299, + "name": "fromSigner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2274, + "src": "14645:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 2298, + "name": "getWithdrawableEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1494, + "src": "14621:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 2300, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "14621:35:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 2301, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2278, + "src": "14658:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 2295, + "name": "_withdraw", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2431, + "src": "14595:9:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_address_$_t_uint256_$_t_bool_$returns$_t_uint256_$", + "typeString": "function (address,address,uint256,bool) returns (uint256)" + } + }, + "id": 2302, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "14595:77:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2284, + "id": 2303, + "nodeType": "Return", + "src": "14588:84:4" + } + ] + }, + "documentation": { + "id": 2272, + "nodeType": "StructuredDocumentation", + "src": "13431:860:4", + "text": "Do an \"unlimited donate withdraw\" on behalf of someone else, to an address they've specified.\nSponsored withdraw is paid by admin, but target account could be whatever the member specifies.\nThe signature gives a \"blank cheque\" for admin to withdraw all tokens to `recipient` in the future,\n and it's valid until next withdraw (and so can be nullified by withdrawing any amount).\nA new signature needs to be obtained for each subsequent future withdraw.\n@param fromSigner whose earnings are being withdrawn\n@param to the address the tokens will be sent to (instead of `msg.sender`)\n@param sendToMainnet if the tokens should be sent to mainnet or only withdrawn into sidechain address\n@param signature from the member, see `signatureIsValid` how signature generated for unlimited amount" + }, + "functionSelector": "ce7b7864", + "id": 2305, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdrawAllToSigned", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2281, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2274, + "mutability": "mutable", + "name": "fromSigner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2305, + "src": "14334:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2273, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "14334:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2276, + "mutability": "mutable", + "name": "to", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2305, + "src": "14362:10:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2275, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "14362:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2278, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2305, + "src": "14382:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2277, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "14382:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2280, + "mutability": "mutable", + "name": "signature", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2305, + "src": "14410:22:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 2279, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "14410:5:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "14324:114:4" + }, + "returnParameters": { + "id": 2284, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2283, + "mutability": "mutable", + "name": "withdrawn", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2305, + "src": "14471:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2282, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "14471:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "14470:16:4" + }, + "scope": 2432, + "src": "14296:383:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2338, + "nodeType": "Block", + "src": "15628:164:4", + "statements": [ + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2323, + "name": "fromSigner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2308, + "src": "15663:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2324, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2310, + "src": "15675:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2325, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2312, + "src": "15679:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 2326, + "name": "signature", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2316, + "src": "15687:9:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "id": 2322, + "name": "signatureIsValid", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2271, + "src": "15646:16:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$_t_address_$_t_uint256_$_t_bytes_memory_ptr_$returns$_t_bool_$", + "typeString": "function (address,address,uint256,bytes memory) view returns (bool)" + } + }, + "id": 2327, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "15646:51:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f6261645369676e6174757265", + "id": 2328, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "15699:20:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_2ec4ea0e2156213061c2a099c127d1a6548cdd3e5b4bf1f695ceebe1aeb9d2b2", + "typeString": "literal_string \"error_badSignature\"" + }, + "value": "error_badSignature" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_2ec4ea0e2156213061c2a099c127d1a6548cdd3e5b4bf1f695ceebe1aeb9d2b2", + "typeString": "literal_string \"error_badSignature\"" + } + ], + "id": 2321, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "15638:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2329, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "15638:82:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2330, + "nodeType": "ExpressionStatement", + "src": "15638:82:4" + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2332, + "name": "fromSigner", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2308, + "src": "15747:10:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2333, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2310, + "src": "15759:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2334, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2312, + "src": "15763:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "id": 2335, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2314, + "src": "15771:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + ], + "id": 2331, + "name": "_withdraw", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2431, + "src": "15737:9:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_nonpayable$_t_address_$_t_address_$_t_uint256_$_t_bool_$returns$_t_uint256_$", + "typeString": "function (address,address,uint256,bool) returns (uint256)" + } + }, + "id": 2336, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "15737:48:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2320, + "id": 2337, + "nodeType": "Return", + "src": "15730:55:4" + } + ] + }, + "documentation": { + "id": 2306, + "nodeType": "StructuredDocumentation", + "src": "14685:725:4", + "text": "Do a \"donate withdraw\" on behalf of someone else, to an address they've specified.\nSponsored withdraw is paid by admin, but target account could be whatever the member specifies.\nThe signature is valid only for given amount of tokens that may be different from maximum withdrawable tokens.\n@param fromSigner whose earnings are being withdrawn\n@param to the address the tokens will be sent to (instead of `msg.sender`)\n@param amount of tokens to withdraw\n@param sendToMainnet if the tokens should be sent to mainnet or only withdrawn into sidechain address\n@param signature from the member, see `signatureIsValid` how signature generated for unlimited amount" + }, + "functionSelector": "1a79246c", + "id": 2339, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "withdrawToSigned", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2317, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2308, + "mutability": "mutable", + "name": "fromSigner", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2339, + "src": "15450:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2307, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "15450:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2310, + "mutability": "mutable", + "name": "to", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2339, + "src": "15478:10:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2309, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "15478:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2312, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2339, + "src": "15498:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2311, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "15498:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2314, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2339, + "src": "15519:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2313, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "15519:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2316, + "mutability": "mutable", + "name": "signature", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2339, + "src": "15547:22:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 2315, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "15547:5:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "15440:135:4" + }, + "returnParameters": { + "id": 2320, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2319, + "mutability": "mutable", + "name": "withdrawn", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2339, + "src": "15608:14:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2318, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "15608:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "15607:16:4" + }, + "scope": 2432, + "src": "15415:377:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2347, + "nodeType": "Block", + "src": "15863:324:4", + "statements": [ + { + "AST": { + "nodeType": "YulBlock", + "src": "15882:299:4", + "statements": [ + { + "nodeType": "YulVariableDeclaration", + "src": "15896:20:4", + "value": { + "arguments": [ + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "15911:4:4", + "type": "", + "value": "0x40" + } + ], + "functionName": { + "name": "mload", + "nodeType": "YulIdentifier", + "src": "15905:5:4" + }, + "nodeType": "YulFunctionCall", + "src": "15905:11:4" + }, + "variables": [ + { + "name": "m", + "nodeType": "YulTypedName", + "src": "15900:1:4", + "type": "" + } + ] + }, + { + "nodeType": "YulAssignment", + "src": "15929:55:4", + "value": { + "arguments": [ + { + "name": "a", + "nodeType": "YulIdentifier", + "src": "15938:1:4" + }, + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "15941:42:4", + "type": "", + "value": "0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + } + ], + "functionName": { + "name": "and", + "nodeType": "YulIdentifier", + "src": "15934:3:4" + }, + "nodeType": "YulFunctionCall", + "src": "15934:50:4" + }, + "variableNames": [ + { + "name": "a", + "nodeType": "YulIdentifier", + "src": "15929:1:4" + } + ] + }, + { + "expression": { + "arguments": [ + { + "arguments": [ + { + "name": "m", + "nodeType": "YulIdentifier", + "src": "16025:1:4" + }, + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "16028:2:4", + "type": "", + "value": "20" + } + ], + "functionName": { + "name": "add", + "nodeType": "YulIdentifier", + "src": "16021:3:4" + }, + "nodeType": "YulFunctionCall", + "src": "16021:10:4" + }, + { + "arguments": [ + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "16053:44:4", + "type": "", + "value": "0x140000000000000000000000000000000000000000" + }, + { + "name": "a", + "nodeType": "YulIdentifier", + "src": "16099:1:4" + } + ], + "functionName": { + "name": "xor", + "nodeType": "YulIdentifier", + "src": "16049:3:4" + }, + "nodeType": "YulFunctionCall", + "src": "16049:52:4" + } + ], + "functionName": { + "name": "mstore", + "nodeType": "YulIdentifier", + "src": "15997:6:4" + }, + "nodeType": "YulFunctionCall", + "src": "15997:118:4" + }, + "nodeType": "YulExpressionStatement", + "src": "15997:118:4" + }, + { + "expression": { + "arguments": [ + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "16135:4:4", + "type": "", + "value": "0x40" + }, + { + "arguments": [ + { + "name": "m", + "nodeType": "YulIdentifier", + "src": "16145:1:4" + }, + { + "kind": "number", + "nodeType": "YulLiteral", + "src": "16148:2:4", + "type": "", + "value": "52" + } + ], + "functionName": { + "name": "add", + "nodeType": "YulIdentifier", + "src": "16141:3:4" + }, + "nodeType": "YulFunctionCall", + "src": "16141:10:4" + } + ], + "functionName": { + "name": "mstore", + "nodeType": "YulIdentifier", + "src": "16128:6:4" + }, + "nodeType": "YulFunctionCall", + "src": "16128:24:4" + }, + "nodeType": "YulExpressionStatement", + "src": "16128:24:4" + }, + { + "nodeType": "YulAssignment", + "src": "16165:6:4", + "value": { + "name": "m", + "nodeType": "YulIdentifier", + "src": "16170:1:4" + }, + "variableNames": [ + { + "name": "b", + "nodeType": "YulIdentifier", + "src": "16165:1:4" + } + ] + } + ] + }, + "evmVersion": "istanbul", + "externalReferences": [ + { + "declaration": 2341, + "isOffset": false, + "isSlot": false, + "src": "15929:1:4", + "valueSize": 1 + }, + { + "declaration": 2341, + "isOffset": false, + "isSlot": false, + "src": "15938:1:4", + "valueSize": 1 + }, + { + "declaration": 2341, + "isOffset": false, + "isSlot": false, + "src": "16099:1:4", + "valueSize": 1 + }, + { + "declaration": 2344, + "isOffset": false, + "isSlot": false, + "src": "16165:1:4", + "valueSize": 1 + } + ], + "id": 2346, + "nodeType": "InlineAssembly", + "src": "15873:308:4" + } + ] + }, + "documentation": null, + "functionSelector": "593b79fe", + "id": 2348, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "toBytes", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2342, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2341, + "mutability": "mutable", + "name": "a", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2348, + "src": "15815:9:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2340, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "15815:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "15814:11:4" + }, + "returnParameters": { + "id": 2345, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2344, + "mutability": "mutable", + "name": "b", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2348, + "src": "15847:14:4", + "stateVariable": false, + "storageLocation": "memory", + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes" + }, + "typeName": { + "id": 2343, + "name": "bytes", + "nodeType": "ElementaryTypeName", + "src": "15847:5:4", + "typeDescriptions": { + "typeIdentifier": "t_bytes_storage_ptr", + "typeString": "bytes" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "15846:16:4" + }, + "scope": 2432, + "src": "15798:389:4", + "stateMutability": "pure", + "virtual": false, + "visibility": "public" + }, + { + "body": { + "id": 2430, + "nodeType": "Block", + "src": "16463:705:4", + "statements": [ + { + "condition": { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 2364, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 2362, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "16477:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "==", + "rightExpression": { + "argumentTypes": null, + "hexValue": "30", + "id": 2363, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "16487:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "src": "16477:11:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": null, + "id": 2367, + "nodeType": "IfStatement", + "src": "16473:25:4", + "trueBody": { + "expression": { + "argumentTypes": null, + "hexValue": "30", + "id": 2365, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "number", + "lValueRequested": false, + "nodeType": "Literal", + "src": "16497:1:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_rational_0_by_1", + "typeString": "int_const 0" + }, + "value": "0" + }, + "functionReturnParameters": 2361, + "id": 2366, + "nodeType": "Return", + "src": "16490:8:4" + } + }, + { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "commonType": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "id": 2373, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftExpression": { + "argumentTypes": null, + "id": 2369, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "16516:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "BinaryOperation", + "operator": "<=", + "rightExpression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2371, + "name": "from", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2351, + "src": "16550:4:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 2370, + "name": "getWithdrawableEarnings", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1494, + "src": "16526:23:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_view$_t_address_$returns$_t_uint256_$", + "typeString": "function (address) view returns (uint256)" + } + }, + "id": 2372, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "16526:29:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "16516:39:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f696e73756666696369656e7442616c616e6365", + "id": 2374, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "16557:27:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_74799d65376d943bac023262731d6421d2e6b6332748a55385eb85a0683327fc", + "typeString": "literal_string \"error_insufficientBalance\"" + }, + "value": "error_insufficientBalance" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_74799d65376d943bac023262731d6421d2e6b6332748a55385eb85a0683327fc", + "typeString": "literal_string \"error_insufficientBalance\"" + } + ], + "id": 2368, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "16508:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2375, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "16508:77:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2376, + "nodeType": "ExpressionStatement", + "src": "16508:77:4" + }, + { + "assignments": [ + 2378 + ], + "declarations": [ + { + "constant": false, + "id": 2378, + "mutability": "mutable", + "name": "info", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2430, + "src": "16595:23:4", + "stateVariable": false, + "storageLocation": "storage", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + }, + "typeName": { + "contractScope": null, + "id": 2377, + "name": "MemberInfo", + "nodeType": "UserDefinedTypeName", + "referencedDeclaration": 1245, + "src": "16595:10:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo" + } + }, + "value": null, + "visibility": "internal" + } + ], + "id": 2382, + "initialValue": { + "argumentTypes": null, + "baseExpression": { + "argumentTypes": null, + "id": 2379, + "name": "memberData", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1269, + "src": "16621:10:4", + "typeDescriptions": { + "typeIdentifier": "t_mapping$_t_address_$_t_struct$_MemberInfo_$1245_storage_$", + "typeString": "mapping(address => struct DataUnionSidechain.MemberInfo storage ref)" + } + }, + "id": 2381, + "indexExpression": { + "argumentTypes": null, + "id": 2380, + "name": "from", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2351, + "src": "16632:4:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "nodeType": "IndexAccess", + "src": "16621:16:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage", + "typeString": "struct DataUnionSidechain.MemberInfo storage ref" + } + }, + "nodeType": "VariableDeclarationStatement", + "src": "16595:42:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 2391, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2383, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2378, + "src": "16647:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 2385, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": true, + "memberName": "withdrawnEarnings", + "nodeType": "MemberAccess", + "referencedDeclaration": 1244, + "src": "16647:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2389, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "16699:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "expression": { + "argumentTypes": null, + "id": 2386, + "name": "info", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2378, + "src": "16672:4:4", + "typeDescriptions": { + "typeIdentifier": "t_struct$_MemberInfo_$1245_storage_ptr", + "typeString": "struct DataUnionSidechain.MemberInfo storage pointer" + } + }, + "id": 2387, + "isConstant": false, + "isLValue": true, + "isPure": false, + "lValueRequested": false, + "memberName": "withdrawnEarnings", + "nodeType": "MemberAccess", + "referencedDeclaration": 1244, + "src": "16672:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2388, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "16672:26:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 2390, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "16672:34:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "16647:59:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2392, + "nodeType": "ExpressionStatement", + "src": "16647:59:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 2398, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "leftHandSide": { + "argumentTypes": null, + "id": 2393, + "name": "totalEarningsWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1255, + "src": "16716:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "nodeType": "Assignment", + "operator": "=", + "rightHandSide": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2396, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "16768:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 2394, + "name": "totalEarningsWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1255, + "src": "16741:22:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2395, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "add", + "nodeType": "MemberAccess", + "referencedDeclaration": 3474, + "src": "16741:26:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_uint256_$_t_uint256_$returns$_t_uint256_$bound_to$_t_uint256_$", + "typeString": "function (uint256,uint256) pure returns (uint256)" + } + }, + "id": 2397, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "16741:34:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "src": "16716:59:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "id": 2399, + "nodeType": "ExpressionStatement", + "src": "16716:59:4" + }, + { + "condition": { + "argumentTypes": null, + "id": 2400, + "name": "sendToMainnet", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2357, + "src": "16789:13:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "falseBody": { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2416, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2353, + "src": "17062:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2417, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "17066:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "expression": { + "argumentTypes": null, + "id": 2414, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "17047:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "id": 2415, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "transfer", + "nodeType": "MemberAccess", + "referencedDeclaration": 4231, + "src": "17047:14:4", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$returns$_t_bool_$", + "typeString": "function (address,uint256) external returns (bool)" + } + }, + "id": 2418, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "17047:26:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f7472616e73666572", + "id": 2419, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "17075:16:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + }, + "value": "error_transfer" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + } + ], + "id": 2413, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "17039:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2420, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "17039:53:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2421, + "nodeType": "ExpressionStatement", + "src": "17039:53:4" + }, + "id": 2422, + "nodeType": "IfStatement", + "src": "16785:307:4", + "trueBody": { + "expression": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2404, + "name": "tokenMediator", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1249, + "src": "16884:13:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2405, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "16919:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2407, + "name": "to", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2353, + "src": "16955:2:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + } + ], + "id": 2406, + "name": "toBytes", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2348, + "src": "16947:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_internal_pure$_t_address_$returns$_t_bytes_memory_ptr_$", + "typeString": "function (address) pure returns (bytes memory)" + } + }, + "id": 2408, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "16947:11:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + { + "typeIdentifier": "t_bytes_memory_ptr", + "typeString": "bytes memory" + } + ], + "expression": { + "argumentTypes": null, + "id": 2402, + "name": "token", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1247, + "src": "16841:5:4", + "typeDescriptions": { + "typeIdentifier": "t_contract$_IERC677_$2939", + "typeString": "contract IERC677" + } + }, + "id": 2403, + "isConstant": false, + "isLValue": false, + "isPure": false, + "lValueRequested": false, + "memberName": "transferAndCall", + "nodeType": "MemberAccess", + "referencedDeclaration": 2938, + "src": "16841:21:4", + "typeDescriptions": { + "typeIdentifier": "t_function_external_nonpayable$_t_address_$_t_uint256_$_t_bytes_memory_ptr_$returns$_t_bool_$", + "typeString": "function (address,uint256,bytes memory) external returns (bool)" + } + }, + "id": 2409, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "16841:135:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + { + "argumentTypes": null, + "hexValue": "6572726f725f7472616e73666572", + "id": 2410, + "isConstant": false, + "isLValue": false, + "isPure": true, + "kind": "string", + "lValueRequested": false, + "nodeType": "Literal", + "src": "16994:16:4", + "subdenomination": null, + "typeDescriptions": { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + }, + "value": "error_transfer" + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + { + "typeIdentifier": "t_stringliteral_a79228fd2655f5e702d118a716d23aa1202e4ee8105c5cccde34ce1d470bb9af", + "typeString": "literal_string \"error_transfer\"" + } + ], + "id": 2401, + "name": "require", + "nodeType": "Identifier", + "overloadedDeclarations": [ + -18, + -18 + ], + "referencedDeclaration": -18, + "src": "16816:7:4", + "typeDescriptions": { + "typeIdentifier": "t_function_require_pure$_t_bool_$_t_string_memory_ptr_$returns$__$", + "typeString": "function (bool,string memory) pure" + } + }, + "id": 2411, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "16816:208:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2412, + "nodeType": "ExpressionStatement", + "src": "16816:208:4" + } + }, + { + "eventCall": { + "argumentTypes": null, + "arguments": [ + { + "argumentTypes": null, + "id": 2424, + "name": "from", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2351, + "src": "17125:4:4", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + { + "argumentTypes": null, + "id": 2425, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "17131:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + } + ], + "expression": { + "argumentTypes": [ + { + "typeIdentifier": "t_address", + "typeString": "address" + }, + { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + ], + "id": 2423, + "name": "EarningsWithdrawn", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 1212, + "src": "17107:17:4", + "typeDescriptions": { + "typeIdentifier": "t_function_event_nonpayable$_t_address_$_t_uint256_$returns$__$", + "typeString": "function (address,uint256)" + } + }, + "id": 2426, + "isConstant": false, + "isLValue": false, + "isPure": false, + "kind": "functionCall", + "lValueRequested": false, + "names": [], + "nodeType": "FunctionCall", + "src": "17107:31:4", + "tryCall": false, + "typeDescriptions": { + "typeIdentifier": "t_tuple$__$", + "typeString": "tuple()" + } + }, + "id": 2427, + "nodeType": "EmitStatement", + "src": "17102:36:4" + }, + { + "expression": { + "argumentTypes": null, + "id": 2428, + "name": "amount", + "nodeType": "Identifier", + "overloadedDeclarations": [], + "referencedDeclaration": 2355, + "src": "17155:6:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "functionReturnParameters": 2361, + "id": 2429, + "nodeType": "Return", + "src": "17148:13:4" + } + ] + }, + "documentation": { + "id": 2349, + "nodeType": "StructuredDocumentation", + "src": "16193:140:4", + "text": "Internal function common to all withdraw methods.\nDoes NOT check proper access, so all callers must do that first." + }, + "id": 2431, + "implemented": true, + "kind": "function", + "modifiers": [], + "name": "_withdraw", + "nodeType": "FunctionDefinition", + "overrides": null, + "parameters": { + "id": 2358, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2351, + "mutability": "mutable", + "name": "from", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2431, + "src": "16357:12:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2350, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "16357:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2353, + "mutability": "mutable", + "name": "to", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2431, + "src": "16371:10:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + }, + "typeName": { + "id": 2352, + "name": "address", + "nodeType": "ElementaryTypeName", + "src": "16371:7:4", + "stateMutability": "nonpayable", + "typeDescriptions": { + "typeIdentifier": "t_address", + "typeString": "address" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2355, + "mutability": "mutable", + "name": "amount", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2431, + "src": "16383:11:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2354, + "name": "uint", + "nodeType": "ElementaryTypeName", + "src": "16383:4:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + }, + { + "constant": false, + "id": 2357, + "mutability": "mutable", + "name": "sendToMainnet", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2431, + "src": "16396:18:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + }, + "typeName": { + "id": 2356, + "name": "bool", + "nodeType": "ElementaryTypeName", + "src": "16396:4:4", + "typeDescriptions": { + "typeIdentifier": "t_bool", + "typeString": "bool" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "16356:59:4" + }, + "returnParameters": { + "id": 2361, + "nodeType": "ParameterList", + "parameters": [ + { + "constant": false, + "id": 2360, + "mutability": "mutable", + "name": "", + "nodeType": "VariableDeclaration", + "overrides": null, + "scope": 2431, + "src": "16450:7:4", + "stateVariable": false, + "storageLocation": "default", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + }, + "typeName": { + "id": 2359, + "name": "uint256", + "nodeType": "ElementaryTypeName", + "src": "16450:7:4", + "typeDescriptions": { + "typeIdentifier": "t_uint256", + "typeString": "uint256" + } + }, + "value": null, + "visibility": "internal" + } + ], + "src": "16449:9:4" + }, + "scope": 2432, + "src": "16338:830:4", + "stateMutability": "nonpayable", + "virtual": false, + "visibility": "internal" + } + ], + "scope": 2433, + "src": "271:16899:4" + } + ], + "src": "0:17171:4" + }, + "compiler": { + "name": "solc", + "version": "0.6.6+commit.6c089d02.Emscripten.clang" + }, + "networks": {}, + "schemaVersion": "3.2.3", + "updatedAt": "2020-12-07T14:56:26.133Z", + "devdoc": { + "methods": { + "claimOwnership()": { + "details": "Allows the pendingOwner address to finalize the transfer." + }, + "signatureIsValid(address,address,uint256,bytes)": { + "params": { + "amount": "how much is authorized for withdraw, or zero for unlimited (withdrawAll)", + "recipient": "of the tokens", + "signature": "byte array from `web3.eth.accounts.sign`", + "signer": "whose earnings are being withdrawn" + }, + "returns": { + "isValid": "true iff signer of the authorization (member whose earnings are going to be withdrawn) matches the signature" + } + }, + "transferOwnership(address)": { + "details": "Allows the current owner to set the pendingOwner address.", + "params": { + "newOwner": "The address to transfer ownership to." + } + }, + "transferWithinContract(address,uint256)": { + "params": { + "amount": "how much withdrawable earnings is transferred", + "recipient": "whose withdrawable earnings will increase" + } + }, + "withdrawAllToSigned(address,address,bool,bytes)": { + "params": { + "fromSigner": "whose earnings are being withdrawn", + "sendToMainnet": "if the tokens should be sent to mainnet or only withdrawn into sidechain address", + "signature": "from the member, see `signatureIsValid` how signature generated for unlimited amount", + "to": "the address the tokens will be sent to (instead of `msg.sender`)" + } + }, + "withdrawToSigned(address,address,uint256,bool,bytes)": { + "params": { + "amount": "of tokens to withdraw", + "fromSigner": "whose earnings are being withdrawn", + "sendToMainnet": "if the tokens should be sent to mainnet or only withdrawn into sidechain address", + "signature": "from the member, see `signatureIsValid` how signature generated for unlimited amount", + "to": "the address the tokens will be sent to (instead of `msg.sender`)" + } + } + } + }, + "userdoc": { + "methods": { + "getStats()": { + "notice": "Atomic getter to get all state variables in one call This alleviates the fact that JSON RPC batch requests aren't available in ethers.js" + }, + "refreshRevenue()": { + "notice": "Process unaccounted tokens that have been sent previously Called by AMB (see DataUnionMainnet:sendTokensToBridge)" + }, + "signatureIsValid(address,address,uint256,bytes)": { + "notice": "Check signature from a member authorizing withdrawing its earnings to another account. Throws if the signature is badly formatted or doesn't match the given signer and amount. Signature has parts the act as replay protection: 1) `address(this)`: signature can't be used for other contracts; 2) `withdrawn[signer]`: signature only works once (for unspecified amount), and can be \"cancelled\" by sending a withdraw tx. Generated in Javascript with: `web3.eth.accounts.sign(recipientAddress + amount.toString(16, 64) + contractAddress.slice(2) + withdrawnTokens.toString(16, 64), signerPrivateKey)`, or for unlimited amount: `web3.eth.accounts.sign(recipientAddress + \"0\".repeat(64) + contractAddress.slice(2) + withdrawnTokens.toString(16, 64), signerPrivateKey)`." + }, + "transferToMemberInContract(address,uint256)": { + "notice": "Transfer tokens from outside contract, add to a recipient's in-contract balance" + }, + "transferWithinContract(address,uint256)": { + "notice": "Transfer tokens from sender's in-contract balance to recipient's in-contract balance This is done by \"withdrawing\" sender's earnings and crediting them to recipient's unwithdrawn earnings, so withdrawnEarnings never decreases for anyone (within this function)" + }, + "withdrawAllToSigned(address,address,bool,bytes)": { + "notice": "Do an \"unlimited donate withdraw\" on behalf of someone else, to an address they've specified. Sponsored withdraw is paid by admin, but target account could be whatever the member specifies. The signature gives a \"blank cheque\" for admin to withdraw all tokens to `recipient` in the future, and it's valid until next withdraw (and so can be nullified by withdrawing any amount). A new signature needs to be obtained for each subsequent future withdraw." + }, + "withdrawToSigned(address,address,uint256,bool,bytes)": { + "notice": "Do a \"donate withdraw\" on behalf of someone else, to an address they've specified. Sponsored withdraw is paid by admin, but target account could be whatever the member specifies. The signature is valid only for given amount of tokens that may be different from maximum withdrawable tokens." + } + } + } +} \ No newline at end of file diff --git a/examples/web/web-example-metamask.html b/examples/web/web-example-metamask.html index 76777b8d9..8798ba341 100644 --- a/examples/web/web-example-metamask.html +++ b/examples/web/web-example-metamask.html @@ -1,46 +1,47 @@ - diff --git a/jest.config.js b/jest.config.js index 5ff6d027a..6dba55d22 100644 --- a/jest.config.js +++ b/jest.config.js @@ -153,7 +153,7 @@ module.exports = { // testResultsProcessor: null, // This option allows use of a custom test runner - // testRunner: "jasmine2", + // testRunner: 'jest-circus/runner', // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href // testURL: "about:blank", diff --git a/package-lock.json b/package-lock.json index e03078dba..9a1991b8c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -327,9 +327,9 @@ "dev": true }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz", - "integrity": "sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.12.tgz", + "integrity": "sha512-nrz9y0a4xmUrRq51bYkWJIO5SBZyG2ys2qinHsN0zHDHVsUaModrkpyWWWXfGqYQmOL3x9sQIcTNN/pBGpo09A==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4", @@ -615,9 +615,9 @@ } }, "@babel/plugin-transform-block-scoping": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.11.tgz", - "integrity": "sha512-atR1Rxc3hM+VPg/NvNvfYw0npQEAcHuJ+MGZnFn6h3bo+1U3BWXMdFMlvVRApBTWKQMX7SOwRJZA5FBF/JQbvA==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.12.tgz", + "integrity": "sha512-VOEPQ/ExOVqbukuP7BYJtI5ZxxsmegTwzZ04j1aF0dkSypGo9XpDHuOrABsJu+ie+penpSJheDJ11x1BEZNiyQ==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.10.4" @@ -1025,26 +1025,26 @@ } }, "@babel/traverse": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.10.tgz", - "integrity": "sha512-6aEtf0IeRgbYWzta29lePeYSk+YAFIC3kyqESeft8o5CkFlYIMX+EQDDWEiAQ9LHOA3d0oHdgrSsID/CKqXJlg==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", + "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.10", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.12.10", - "@babel/types": "^7.12.10", + "@babel/code-frame": "^7.12.11", + "@babel/generator": "^7.12.11", + "@babel/helper-function-name": "^7.12.11", + "@babel/helper-split-export-declaration": "^7.12.11", + "@babel/parser": "^7.12.11", + "@babel/types": "^7.12.12", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.11.tgz", - "integrity": "sha512-ukA9SQtKThINm++CX1CwmliMrE54J6nIYB5XTwL5f/CLFW9owfls+YSU8tVW15RQ2w+a3fSbPjC6HdQNtWZkiA==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", + "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.12.11", @@ -1068,10 +1068,16 @@ "minimist": "^1.2.0" } }, + "@discoveryjs/json-ext": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz", + "integrity": "sha512-HyYEUDeIj5rRQU2Hk5HTB2uHsbRQpF70nvMhVzi+VJR0X+xNEhjPui4/kBf3VeH/wqD28PT4sVOm8qqLjBrSZg==", + "dev": true + }, "@eslint/eslintrc": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.2.tgz", - "integrity": "sha512-EfB5OHNYp1F4px/LI/FEnGylop7nOqkQ1LRzCM0KccA2U8tvV8w01KBv37LbO7nW4H+YhKyo2LcJhRwjjV17QQ==", + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.3.0.tgz", + "integrity": "sha512-1JTKgrOKAHVivSvOYw+sJOunkBjUOvjqWk1DPja7ZFhIS2mX/4EgTT8M7eTK9jrKhL/FvXXEbQwIs3pg1xp3dg==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -1081,7 +1087,7 @@ "ignore": "^4.0.6", "import-fresh": "^3.2.1", "js-yaml": "^3.13.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "strip-json-comments": "^3.1.1" }, @@ -1104,254 +1110,179 @@ } }, "@ethersproject/abi": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.0.9.tgz", - "integrity": "sha512-ily2OufA2DTrxkiHQw5GqbkMSnNKuwZBqKsajtT0ERhZy1r9w2CpW1bmtRMIGzaqQxCdn/GEoFogexk72cBBZQ==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.0.10.tgz", + "integrity": "sha512-cfC3lGgotfxX3SMri4+CisOPwignoj/QGHW9J29spC4R4Qqcnk/SYuVkPFBMdLbvBp3f/pGiVqPNwont0TSXhg==", "requires": { - "@ethersproject/address": "^5.0.4", - "@ethersproject/bignumber": "^5.0.7", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/constants": "^5.0.4", - "@ethersproject/hash": "^5.0.4", - "@ethersproject/keccak256": "^5.0.3", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/properties": "^5.0.3", - "@ethersproject/strings": "^5.0.4" + "@ethersproject/address": "^5.0.9", + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/constants": "^5.0.8", + "@ethersproject/hash": "^5.0.10", + "@ethersproject/keccak256": "^5.0.7", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/strings": "^5.0.8" } }, "@ethersproject/abstract-provider": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.0.7.tgz", - "integrity": "sha512-NF16JGn6M0zZP5ZS8KtDL2Rh7yHxZbUjBIHLNHMm/0X0BephhjUWy8jqs/Zks6kDJRzNthgmPVy41Ec0RYWPYA==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.0.8.tgz", + "integrity": "sha512-fqJXkewcGdi8LogKMgRyzc/Ls2js07yor7+g9KfPs09uPOcQLg7cc34JN+lk34HH9gg2HU0DIA5797ZR8znkfw==", "requires": { - "@ethersproject/bignumber": "^5.0.7", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/networks": "^5.0.3", - "@ethersproject/properties": "^5.0.3", - "@ethersproject/transactions": "^5.0.5", - "@ethersproject/web": "^5.0.6" + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/networks": "^5.0.7", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/transactions": "^5.0.9", + "@ethersproject/web": "^5.0.12" } }, "@ethersproject/abstract-signer": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.0.9.tgz", - "integrity": "sha512-CM5UNmXQaA03MyYARFDDRjHWBxujO41tVle7glf5kHcQsDDULgqSVpkliLJMtPzZjOKFeCVZBHybTZDEZg5zzg==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.0.11.tgz", + "integrity": "sha512-RKOgPSEYafknA62SrD3OCK42AllHE4YBfKYXyQeM+sBP7Nq3X5FpzeoY4uzC43P4wIhmNoTHCKQuwnX7fBqb6Q==", "requires": { - "@ethersproject/abstract-provider": "^5.0.4", - "@ethersproject/bignumber": "^5.0.7", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/properties": "^5.0.3" + "@ethersproject/abstract-provider": "^5.0.8", + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7" } }, "@ethersproject/address": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.0.8.tgz", - "integrity": "sha512-V87DHiZMZR6hmFYmoGaHex0D53UEbZpW75uj8AqPbjYUmi65RB4N2LPRcJXuWuN2R0Y2CxkvW6ArijWychr5FA==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.0.9.tgz", + "integrity": "sha512-gKkmbZDMyGbVjr8nA5P0md1GgESqSGH7ILIrDidPdNXBl4adqbuA3OAuZx/O2oGpL6PtJ9BDa0kHheZ1ToHU3w==", "requires": { - "@ethersproject/bignumber": "^5.0.10", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/keccak256": "^5.0.3", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/rlp": "^5.0.3" + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/keccak256": "^5.0.7", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/rlp": "^5.0.7" } }, "@ethersproject/base64": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.0.6.tgz", - "integrity": "sha512-HwrGn8YMiUf7bcdVvB4NJ+eWT0BtEFpDtrYxVXEbR7p/XBSJjwiR7DEggIiRvxbualMKg+EZijQWJ3az2li0uw==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.0.7.tgz", + "integrity": "sha512-S5oh5DVfCo06xwJXT8fQC68mvJfgScTl2AXvbYMsHNfIBTDb084Wx4iA9MNlEReOv6HulkS+gyrUM/j3514rSw==", "requires": { - "@ethersproject/bytes": "^5.0.4" - }, - "dependencies": { - "@ethersproject/bytes": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.9.tgz", - "integrity": "sha512-k+17ZViDtAugC0s7HM6rdsTWEdIYII4RPCDkPEuxKc6i40Bs+m6tjRAtCECX06wKZnrEoR9pjOJRXHJ/VLoOcA==", - "requires": { - "@ethersproject/logger": "^5.0.8" - } - }, - "@ethersproject/logger": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", - "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" - } + "@ethersproject/bytes": "^5.0.9" } }, "@ethersproject/basex": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.0.6.tgz", - "integrity": "sha512-Y/8dowRxBF3bsKkqEp7XN4kcFFQ0o5xxP1YyopfqkXejaOEGiD7ToQdQ0pIZpAJ5GreW56oFOTDDSO6ZcUCNYg==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.0.7.tgz", + "integrity": "sha512-OsXnRsujGmYD9LYyJlX+cVe5KfwgLUbUJrJMWdzRWogrygXd5HvGd7ygX1AYjlu1z8W/+t2FoQnczDR/H2iBjA==", "requires": { - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/properties": "^5.0.3" - }, - "dependencies": { - "@ethersproject/bytes": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.9.tgz", - "integrity": "sha512-k+17ZViDtAugC0s7HM6rdsTWEdIYII4RPCDkPEuxKc6i40Bs+m6tjRAtCECX06wKZnrEoR9pjOJRXHJ/VLoOcA==", - "requires": { - "@ethersproject/logger": "^5.0.8" - } - }, - "@ethersproject/logger": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", - "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" - }, - "@ethersproject/properties": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.7.tgz", - "integrity": "sha512-812H1Rus2vjw0zbasfDI1GLNPDsoyX1pYqiCgaR1BuyKxUTbwcH1B+214l6VGe1v+F6iEVb7WjIwMjKhb4EUsg==", - "requires": { - "@ethersproject/logger": "^5.0.8" - } - } + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/properties": "^5.0.7" } }, "@ethersproject/bignumber": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.12.tgz", - "integrity": "sha512-mbFZjwthx6vFlHG9owXP/C5QkNvsA+xHpDCkPPPdG2n1dS9AmZAL5DI0InNLid60rQWL3MXpEl19tFmtL7Q9jw==", + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.13.tgz", + "integrity": "sha512-b89bX5li6aK492yuPP5mPgRVgIxxBP7ksaBtKX5QQBsrZTpNOjf/MR4CjcUrAw8g+RQuD6kap9lPjFgY4U1/5A==", "requires": { - "@ethersproject/bytes": "^5.0.8", - "@ethersproject/logger": "^5.0.5", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", "bn.js": "^4.4.0" - }, - "dependencies": { - "@ethersproject/bytes": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.9.tgz", - "integrity": "sha512-k+17ZViDtAugC0s7HM6rdsTWEdIYII4RPCDkPEuxKc6i40Bs+m6tjRAtCECX06wKZnrEoR9pjOJRXHJ/VLoOcA==", - "requires": { - "@ethersproject/logger": "^5.0.8" - } - }, - "@ethersproject/logger": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", - "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" - } } }, "@ethersproject/bytes": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.8.tgz", - "integrity": "sha512-O+sJNVGzzuy51g+EMK8BegomqNIg+C2RO6vOt0XP6ac4o4saiq69FnjlsrNslaiMFVO7qcEHBsWJ9hx1tj1lMw==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.9.tgz", + "integrity": "sha512-k+17ZViDtAugC0s7HM6rdsTWEdIYII4RPCDkPEuxKc6i40Bs+m6tjRAtCECX06wKZnrEoR9pjOJRXHJ/VLoOcA==", "requires": { - "@ethersproject/logger": "^5.0.5" - }, - "dependencies": { - "@ethersproject/logger": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", - "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" - } + "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/constants": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.7.tgz", - "integrity": "sha512-cbQK1UpE4hamB52Eg6DLhJoXeQ1plSzekh5Ujir1xdREdwdsZPPXKczkrWqBBR0KyywJZHN/o/hj0w8j7scSGg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.8.tgz", + "integrity": "sha512-sCc73pFBsl59eDfoQR5OCEZCRv5b0iywadunti6MQIr5lt3XpwxK1Iuzd8XSFO02N9jUifvuZRrt0cY0+NBgTg==", "requires": { - "@ethersproject/bignumber": "^5.0.7" + "@ethersproject/bignumber": "^5.0.13" } }, "@ethersproject/contracts": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.0.8.tgz", - "integrity": "sha512-PecBL4vnsrpuks2lzzkRsOts8csJy338HNDKDIivbFmx92BVzh3ohOOv3XsoYPSXIHQvobF959W+aSk3RCZL/g==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.0.9.tgz", + "integrity": "sha512-CCTxVeDh6sjdSEbjzONhtwPjECvaHE62oGkY8M7kP0CHmgLD2SEGel0HZib8e5oQKRKGly9AKcUFW4g3rQ0AQw==", "requires": { - "@ethersproject/abi": "^5.0.5", - "@ethersproject/abstract-provider": "^5.0.4", - "@ethersproject/abstract-signer": "^5.0.4", - "@ethersproject/address": "^5.0.4", - "@ethersproject/bignumber": "^5.0.7", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/constants": "^5.0.4", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/properties": "^5.0.3" + "@ethersproject/abi": "^5.0.10", + "@ethersproject/abstract-provider": "^5.0.8", + "@ethersproject/abstract-signer": "^5.0.10", + "@ethersproject/address": "^5.0.9", + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/constants": "^5.0.8", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7" } }, "@ethersproject/hash": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.0.9.tgz", - "integrity": "sha512-e8/i2ZDeGSgCxXT0vocL54+pMbw5oX5fNjb2E3bAIvdkh5kH29M7zz1jHu1QDZnptIuvCZepIbhUH8lxKE2/SQ==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.0.10.tgz", + "integrity": "sha512-Tf0bvs6YFhw28LuHnhlDWyr0xfcDxSXdwM4TcskeBbmXVSKLv3bJQEEEBFUcRX0fJuslR3gCVySEaSh7vuMx5w==", "requires": { - "@ethersproject/abstract-signer": "^5.0.6", - "@ethersproject/address": "^5.0.5", - "@ethersproject/bignumber": "^5.0.8", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/keccak256": "^5.0.3", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/properties": "^5.0.4", - "@ethersproject/strings": "^5.0.4" + "@ethersproject/abstract-signer": "^5.0.10", + "@ethersproject/address": "^5.0.9", + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/keccak256": "^5.0.7", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/strings": "^5.0.8" } }, "@ethersproject/hdnode": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.0.7.tgz", - "integrity": "sha512-89tphqlji4y/LNE1cSaMQ3hrBtJ4lO1qWGi2hn54LiHym85DTw+zAKbA8QgmdSdJDLGR/kc9VHaIPQ+vZQ2LkQ==", - "requires": { - "@ethersproject/abstract-signer": "^5.0.4", - "@ethersproject/basex": "^5.0.3", - "@ethersproject/bignumber": "^5.0.7", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/pbkdf2": "^5.0.3", - "@ethersproject/properties": "^5.0.3", - "@ethersproject/sha2": "^5.0.3", - "@ethersproject/signing-key": "^5.0.4", - "@ethersproject/strings": "^5.0.4", - "@ethersproject/transactions": "^5.0.5", - "@ethersproject/wordlists": "^5.0.4" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.0.8.tgz", + "integrity": "sha512-Mscpjd7BBjxYSWghaNMwV0xrBBkOoCq6YEPRm9MgE24CiBlzzbfEB5DGq6hiZqhQaxPkdCUtKKqZi3nt9hx43g==", + "requires": { + "@ethersproject/abstract-signer": "^5.0.10", + "@ethersproject/basex": "^5.0.7", + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/pbkdf2": "^5.0.7", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/sha2": "^5.0.7", + "@ethersproject/signing-key": "^5.0.8", + "@ethersproject/strings": "^5.0.8", + "@ethersproject/transactions": "^5.0.9", + "@ethersproject/wordlists": "^5.0.8" } }, "@ethersproject/json-wallets": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.0.9.tgz", - "integrity": "sha512-EWuFvJd8nu90dkmJwmJddxOYCvFvMkKBsZi8rxTme2XEZsHKOFnybVkoL23u7ZtApuEfTKmVcR2PTwgZwqDsKw==", - "requires": { - "@ethersproject/abstract-signer": "^5.0.4", - "@ethersproject/address": "^5.0.4", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/hdnode": "^5.0.4", - "@ethersproject/keccak256": "^5.0.3", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/pbkdf2": "^5.0.3", - "@ethersproject/properties": "^5.0.3", - "@ethersproject/random": "^5.0.3", - "@ethersproject/strings": "^5.0.4", - "@ethersproject/transactions": "^5.0.5", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.0.10.tgz", + "integrity": "sha512-Ux36u+d7Dm0M5AQ+mWuHdvfGPMN8K1aaLQgwzrsD4ELTWlwRuHuQbmn7/GqeOpbfaV6POLwdYcBk2TXjlGp/IQ==", + "requires": { + "@ethersproject/abstract-signer": "^5.0.10", + "@ethersproject/address": "^5.0.9", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/hdnode": "^5.0.8", + "@ethersproject/keccak256": "^5.0.7", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/pbkdf2": "^5.0.7", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/random": "^5.0.7", + "@ethersproject/strings": "^5.0.8", + "@ethersproject/transactions": "^5.0.9", "aes-js": "3.0.0", "scrypt-js": "3.0.1" } }, "@ethersproject/keccak256": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.0.6.tgz", - "integrity": "sha512-eJ4Id/i2rwrf5JXEA7a12bG1phuxjj47mPZgDUbttuNBodhSuZF2nEO5QdpaRjmlphQ8Kt9PNqY/z7lhtJptZg==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.0.7.tgz", + "integrity": "sha512-zpUBmofWvx9PGfc7IICobgFQSgNmTOGTGLUxSYqZzY/T+b4y/2o5eqf/GGmD7qnTGzKQ42YlLNo+LeDP2qe55g==", "requires": { - "@ethersproject/bytes": "^5.0.4", + "@ethersproject/bytes": "^5.0.9", "js-sha3": "0.5.7" - }, - "dependencies": { - "@ethersproject/bytes": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.9.tgz", - "integrity": "sha512-k+17ZViDtAugC0s7HM6rdsTWEdIYII4RPCDkPEuxKc6i40Bs+m6tjRAtCECX06wKZnrEoR9pjOJRXHJ/VLoOcA==", - "requires": { - "@ethersproject/logger": "^5.0.8" - } - }, - "@ethersproject/logger": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", - "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" - } } }, "@ethersproject/logger": { @@ -1360,100 +1291,52 @@ "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" }, "@ethersproject/networks": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.0.6.tgz", - "integrity": "sha512-2Cg1N5109zzFOBfkyuPj+FfF7ioqAsRffmybJ2lrsiB5skphIAE72XNSCs4fqktlf+rwSh/5o/UXRjXxvSktZw==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.0.7.tgz", + "integrity": "sha512-dI14QATndIcUgcCBL1c5vUr/YsI5cCHLN81rF7PU+yS7Xgp2/Rzbr9+YqpC6NBXHFUASjh6GpKqsVMpufAL0BQ==", "requires": { - "@ethersproject/logger": "^5.0.5" - }, - "dependencies": { - "@ethersproject/logger": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", - "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" - } + "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/pbkdf2": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.0.6.tgz", - "integrity": "sha512-CUYciSxR/AaCoKMJk3WUW+BDhR41G3C+O9lOeZ4bR1wDhLKL2Z8p0ciF5XDEiVbmI4CToW6boVKybeVMdngRrg==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.0.7.tgz", + "integrity": "sha512-0SNLNixPMqnosH6pyc4yPiUu/C9/Jbu+f6I8GJW9U2qNpMBddmRJviwseoha5Zw1V+Aw0Z/yvYyzIIE8yPXqLA==", "requires": { - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/sha2": "^5.0.3" - }, - "dependencies": { - "@ethersproject/bytes": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.9.tgz", - "integrity": "sha512-k+17ZViDtAugC0s7HM6rdsTWEdIYII4RPCDkPEuxKc6i40Bs+m6tjRAtCECX06wKZnrEoR9pjOJRXHJ/VLoOcA==", - "requires": { - "@ethersproject/logger": "^5.0.8" - } - }, - "@ethersproject/logger": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", - "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" - }, - "@ethersproject/sha2": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.0.7.tgz", - "integrity": "sha512-MbUqz68hhp5RsaZdqi1eg1rrtiqt5wmhRYqdA7MX8swBkzW2KiLgK+Oh25UcWhUhdi1ImU9qrV6if5j0cC7Bxg==", - "requires": { - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/logger": "^5.0.8", - "hash.js": "1.1.3" - } - }, - "hash.js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", - "requires": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.0" - } - } + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/sha2": "^5.0.7" } }, "@ethersproject/properties": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.6.tgz", - "integrity": "sha512-a9DUMizYhJ0TbtuDkO9iYlb2CDlpSKqGPDr+amvlZhRspQ6jbl5Eq8jfu4SCcGlcfaTbguJmqGnyOGn1EFt6xA==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.7.tgz", + "integrity": "sha512-812H1Rus2vjw0zbasfDI1GLNPDsoyX1pYqiCgaR1BuyKxUTbwcH1B+214l6VGe1v+F6iEVb7WjIwMjKhb4EUsg==", "requires": { - "@ethersproject/logger": "^5.0.5" - }, - "dependencies": { - "@ethersproject/logger": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", - "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" - } + "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/providers": { - "version": "5.0.17", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.0.17.tgz", - "integrity": "sha512-bJnvs5X7ttU5x2ekGJYG7R3Z+spZawLFfR0IDsbaMDLiCwZOyrgk+VTBU7amSFLT0WUhWFv8WwSUB+AryCQG1Q==", - "requires": { - "@ethersproject/abstract-provider": "^5.0.4", - "@ethersproject/abstract-signer": "^5.0.4", - "@ethersproject/address": "^5.0.4", - "@ethersproject/basex": "^5.0.3", - "@ethersproject/bignumber": "^5.0.7", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/constants": "^5.0.4", - "@ethersproject/hash": "^5.0.4", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/networks": "^5.0.3", - "@ethersproject/properties": "^5.0.3", - "@ethersproject/random": "^5.0.3", - "@ethersproject/rlp": "^5.0.3", - "@ethersproject/sha2": "^5.0.3", - "@ethersproject/strings": "^5.0.4", - "@ethersproject/transactions": "^5.0.5", - "@ethersproject/web": "^5.0.6", + "version": "5.0.19", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.0.19.tgz", + "integrity": "sha512-G+flo1jK1y/rvQy6b71+Nu7qOlkOKz+XqpgqFMZslkCzGuzQRmk9Qp7Ln4soK8RSyP1e5TCujaRf1H+EZahoaw==", + "requires": { + "@ethersproject/abstract-provider": "^5.0.8", + "@ethersproject/abstract-signer": "^5.0.10", + "@ethersproject/address": "^5.0.9", + "@ethersproject/basex": "^5.0.7", + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/constants": "^5.0.8", + "@ethersproject/hash": "^5.0.10", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/networks": "^5.0.7", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/random": "^5.0.7", + "@ethersproject/rlp": "^5.0.7", + "@ethersproject/sha2": "^5.0.7", + "@ethersproject/strings": "^5.0.8", + "@ethersproject/transactions": "^5.0.9", + "@ethersproject/web": "^5.0.12", "bech32": "1.1.4", "ws": "7.2.3" }, @@ -1466,76 +1349,33 @@ } }, "@ethersproject/random": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.0.6.tgz", - "integrity": "sha512-8nsVNaZvZ9OD5NXfzE4mmz8IH/1DYJbAR95xpRxZkIuNmfn6QlMp49ccJYZWGhs6m0Zj2+FXjx3pzXfYlo9/dA==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.0.7.tgz", + "integrity": "sha512-PxSRWwN3s+FH9AWMZU6AcWJsNQ9KzqKV6NgdeKPtxahdDjCuXxTAuzTZNXNRK+qj+Il351UnweAGd+VuZcOAlQ==", "requires": { - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/logger": "^5.0.5" - }, - "dependencies": { - "@ethersproject/bytes": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.9.tgz", - "integrity": "sha512-k+17ZViDtAugC0s7HM6rdsTWEdIYII4RPCDkPEuxKc6i40Bs+m6tjRAtCECX06wKZnrEoR9pjOJRXHJ/VLoOcA==", - "requires": { - "@ethersproject/logger": "^5.0.8" - } - }, - "@ethersproject/logger": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", - "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" - } + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/rlp": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.0.6.tgz", - "integrity": "sha512-M223MTaydfmQSsvqAl0FJZDYFlSqt6cgbhnssLDwqCKYegAHE16vrFyo+eiOapYlt32XAIJm0BXlqSunULzZuQ==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.0.7.tgz", + "integrity": "sha512-ulUTVEuV7PT4jJTPpfhRHK57tkLEDEY9XSYJtrSNHOqdwMvH0z7BM2AKIMq4LVDlnu4YZASdKrkFGEIO712V9w==", "requires": { - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/logger": "^5.0.5" - }, - "dependencies": { - "@ethersproject/bytes": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.9.tgz", - "integrity": "sha512-k+17ZViDtAugC0s7HM6rdsTWEdIYII4RPCDkPEuxKc6i40Bs+m6tjRAtCECX06wKZnrEoR9pjOJRXHJ/VLoOcA==", - "requires": { - "@ethersproject/logger": "^5.0.8" - } - }, - "@ethersproject/logger": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", - "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" - } + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/sha2": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.0.6.tgz", - "integrity": "sha512-30gypDLkfkP5gE3llqi0jEuRV8m4/nvzeqmqMxiihZ7veFQHqDaGpyFeHzFim+qGeH9fq0lgYjavLvwW69+Fkw==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.0.7.tgz", + "integrity": "sha512-MbUqz68hhp5RsaZdqi1eg1rrtiqt5wmhRYqdA7MX8swBkzW2KiLgK+Oh25UcWhUhdi1ImU9qrV6if5j0cC7Bxg==", "requires": { - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/logger": "^5.0.5", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", "hash.js": "1.1.3" }, "dependencies": { - "@ethersproject/bytes": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.9.tgz", - "integrity": "sha512-k+17ZViDtAugC0s7HM6rdsTWEdIYII4RPCDkPEuxKc6i40Bs+m6tjRAtCECX06wKZnrEoR9pjOJRXHJ/VLoOcA==", - "requires": { - "@ethersproject/logger": "^5.0.8" - } - }, - "@ethersproject/logger": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", - "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" - }, "hash.js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", @@ -1548,131 +1388,108 @@ } }, "@ethersproject/signing-key": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.0.7.tgz", - "integrity": "sha512-JYndnhFPKH0daPcIjyhi+GMcw3srIHkQ40hGRe6DA0CdGrpMfgyfSYDQ2D8HL2lgR+Xm4SHfEB0qba6+sCyrvg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.0.8.tgz", + "integrity": "sha512-YKxQM45eDa6WAD+s3QZPdm1uW1MutzVuyoepdRRVmMJ8qkk7iOiIhUkZwqKLNxKzEJijt/82ycuOREc9WBNAKg==", "requires": { - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/properties": "^5.0.3", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7", "elliptic": "6.5.3" - }, - "dependencies": { - "@ethersproject/bytes": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.9.tgz", - "integrity": "sha512-k+17ZViDtAugC0s7HM6rdsTWEdIYII4RPCDkPEuxKc6i40Bs+m6tjRAtCECX06wKZnrEoR9pjOJRXHJ/VLoOcA==", - "requires": { - "@ethersproject/logger": "^5.0.8" - } - }, - "@ethersproject/logger": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", - "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" - }, - "@ethersproject/properties": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.7.tgz", - "integrity": "sha512-812H1Rus2vjw0zbasfDI1GLNPDsoyX1pYqiCgaR1BuyKxUTbwcH1B+214l6VGe1v+F6iEVb7WjIwMjKhb4EUsg==", - "requires": { - "@ethersproject/logger": "^5.0.8" - } - } } }, "@ethersproject/solidity": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.0.7.tgz", - "integrity": "sha512-dUevKUZ06p/VMLP/+cz4QUV+lA17NixucDJfm0ioWF0B3R0Lf+6wqwPchcqiAXlxkNFGIax7WNLgGMh4CkQ8iw==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.0.8.tgz", + "integrity": "sha512-OJkyBq9KaoGsi8E8mYn6LX+vKyCURvxSp0yuGBcOqEFM3vkn9PsCiXsHdOXdNBvlHG5evJXwAYC2UR0TzgJeKA==", "requires": { - "@ethersproject/bignumber": "^5.0.7", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/keccak256": "^5.0.3", - "@ethersproject/sha2": "^5.0.3", - "@ethersproject/strings": "^5.0.4" + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/keccak256": "^5.0.7", + "@ethersproject/sha2": "^5.0.7", + "@ethersproject/strings": "^5.0.8" } }, "@ethersproject/strings": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.7.tgz", - "integrity": "sha512-a+6T80LvmXGMOOWQTZHtGGQEg1z4v8rm8oX70KNs55YtPXI/5J3LBbVf5pyqCKSlmiBw5IaepPvs5XGalRUSZQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.8.tgz", + "integrity": "sha512-5IsdXf8tMY8QuHl8vTLnk9ehXDDm6x9FB9S9Og5IA1GYhLe5ZewydXSjlJlsqU2t9HRbfv97OJZV/pX8DVA/Hw==", "requires": { - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/constants": "^5.0.4", - "@ethersproject/logger": "^5.0.5" + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/constants": "^5.0.8", + "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/transactions": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.0.8.tgz", - "integrity": "sha512-i7NtOXVzUe+YSU6QufzlRrI2WzHaTmULAKHJv4duIZMLqzehCBXGA9lTpFgFdqGYcQJ7vOtNFC2BB2mSjmuXqg==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.0.9.tgz", + "integrity": "sha512-0Fu1yhdFBkrbMjenEr+39tmDxuHmaw0pe9Jb18XuKoItj7Z3p7+UzdHLr2S/okvHDHYPbZE5gtANDdQ3ZL1nBA==", "requires": { - "@ethersproject/address": "^5.0.4", - "@ethersproject/bignumber": "^5.0.7", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/constants": "^5.0.4", - "@ethersproject/keccak256": "^5.0.3", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/properties": "^5.0.3", - "@ethersproject/rlp": "^5.0.3", - "@ethersproject/signing-key": "^5.0.4" + "@ethersproject/address": "^5.0.9", + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/constants": "^5.0.8", + "@ethersproject/keccak256": "^5.0.7", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/rlp": "^5.0.7", + "@ethersproject/signing-key": "^5.0.8" } }, "@ethersproject/units": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.0.8.tgz", - "integrity": "sha512-3O4MaNHFs05vC5v2ZGqVFVWtO1WyqFejO78M7Qh16njo282aoMlENtVI6cn2B36zOLFXRvYt2pYx6xCG53qKzg==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.0.9.tgz", + "integrity": "sha512-4jIkcMVrJ3lCgXMO4M/2ww0/T/IN08vJTZld7FIAwa6aoBDTAy71+sby3sShl1SG3HEeKYbI3fBWauCUgPRUpQ==", "requires": { - "@ethersproject/bignumber": "^5.0.7", - "@ethersproject/constants": "^5.0.4", - "@ethersproject/logger": "^5.0.5" + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/constants": "^5.0.8", + "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/wallet": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.0.9.tgz", - "integrity": "sha512-GfpQF56PO/945SJq7Wdg5F5U6wkxaDgkAzcgGbCW6Joz8oW8MzKItkvYCzMh+j/8gJMzFncsuqX4zg2gq3J6nQ==", - "requires": { - "@ethersproject/abstract-provider": "^5.0.4", - "@ethersproject/abstract-signer": "^5.0.4", - "@ethersproject/address": "^5.0.4", - "@ethersproject/bignumber": "^5.0.7", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/hash": "^5.0.4", - "@ethersproject/hdnode": "^5.0.4", - "@ethersproject/json-wallets": "^5.0.6", - "@ethersproject/keccak256": "^5.0.3", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/properties": "^5.0.3", - "@ethersproject/random": "^5.0.3", - "@ethersproject/signing-key": "^5.0.4", - "@ethersproject/transactions": "^5.0.5", - "@ethersproject/wordlists": "^5.0.4" + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.0.10.tgz", + "integrity": "sha512-5siYr38NhqZKH6DUr6u4PdhgOKur8Q6sw+JID2TitEUmW0tOl8f6rpxAe77tw6SJT60D2UcvgsyLtl32+Nl+ig==", + "requires": { + "@ethersproject/abstract-provider": "^5.0.8", + "@ethersproject/abstract-signer": "^5.0.10", + "@ethersproject/address": "^5.0.9", + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/hash": "^5.0.10", + "@ethersproject/hdnode": "^5.0.8", + "@ethersproject/json-wallets": "^5.0.10", + "@ethersproject/keccak256": "^5.0.7", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/random": "^5.0.7", + "@ethersproject/signing-key": "^5.0.8", + "@ethersproject/transactions": "^5.0.9", + "@ethersproject/wordlists": "^5.0.8" } }, "@ethersproject/web": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.0.11.tgz", - "integrity": "sha512-x03ihbPoN1S8Gsh9WSwxkYxUIumLi02ZEKJku1C43sxBfe+mdprWyvujzYlpuoRNfWRgNhdRDKMP8JbG6MwNGA==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.0.12.tgz", + "integrity": "sha512-gVxS5iW0bgidZ76kr7LsTxj4uzN5XpCLzvZrLp8TP+4YgxHfCeetFyQkRPgBEAJdNrexdSBayvyJvzGvOq0O8g==", "requires": { - "@ethersproject/base64": "^5.0.3", - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/properties": "^5.0.3", - "@ethersproject/strings": "^5.0.4" + "@ethersproject/base64": "^5.0.7", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/strings": "^5.0.8" } }, "@ethersproject/wordlists": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.0.7.tgz", - "integrity": "sha512-ZjQtYxm41FmHfYgpkdQG++EDcBPQWv9O6FfP6NndYRVaXaQZh6eq3sy7HQP8zCZ8dznKgy6ZyKECS8qdvnGHwA==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.0.8.tgz", + "integrity": "sha512-px2mloc1wAcdTbzv0ZotTx+Uh/dfnDO22D9Rx8xr7+/PUwAhZQjoJ9t7Hn72nsaN83rWBXsLvFcIRZju4GIaEQ==", "requires": { - "@ethersproject/bytes": "^5.0.4", - "@ethersproject/hash": "^5.0.4", - "@ethersproject/logger": "^5.0.5", - "@ethersproject/properties": "^5.0.3", - "@ethersproject/strings": "^5.0.4" + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/hash": "^5.0.10", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/strings": "^5.0.8" } }, "@istanbuljs/load-nyc-config": { @@ -2341,12 +2158,24 @@ } }, "@npmcli/move-file": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", - "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.0.tgz", + "integrity": "sha512-Iv2iq0JuyYjKeFkSR4LPaCdDZwlGK9X2cP/01nJcp3yMJ1FjNd9vpiEYvLUgzBxKPg2SFmaOhizoQsPc0LWeOQ==", "dev": true, "requires": { - "mkdirp": "^1.0.4" + "mkdirp": "^1.0.4", + "rimraf": "^2.7.1" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "@peculiar/asn1-schema": { @@ -2392,20 +2221,10 @@ "@sinonjs/commons": "^1.7.0" } }, - "@sinonjs/formatio": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-5.0.1.tgz", - "integrity": "sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^5.0.2" - } - }, "@sinonjs/samsam": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.0.tgz", - "integrity": "sha512-hXpcfx3aq+ETVBwPlRFICld5EnrkexXuXDwqUNhDdr5L8VjvMeSRwyOa0qL7XFmR+jVWR4rUZtnxlG7RX72sBg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz", + "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==", "dev": true, "requires": { "@sinonjs/commons": "^1.6.0", @@ -2533,9 +2352,9 @@ "dev": true }, "@types/node": { - "version": "14.14.14", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.14.tgz", - "integrity": "sha512-UHnOPWVWV1z+VV8k6L1HhG7UbGBgIdghqF3l9Ny9ApPghbjICXkUJSd/b9gOgQfjM1r+37cipdw/HJ3F6ICEnQ==", + "version": "14.14.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.21.tgz", + "integrity": "sha512-cHYfKsnwllYhjOzuC5q1VpguABBeecUp24yFluHpn/BQaVxB1CuQ1FSRZCzrPxrkIfWISXV2LbeoBthLWg0+0A==", "dev": true }, "@types/normalize-package-data": { @@ -2545,9 +2364,9 @@ "dev": true }, "@types/prettier": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.5.tgz", - "integrity": "sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.6.tgz", + "integrity": "sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA==", "dev": true }, "@types/stack-utils": { @@ -2757,18 +2576,18 @@ } }, "@webpack-cli/info": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.1.0.tgz", - "integrity": "sha512-uNWSdaYHc+f3LdIZNwhdhkjjLDDl3jP2+XBqAq9H8DjrJUvlOKdP8TNruy1yEaDfgpAIgbSAN7pye4FEHg9tYQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.1.tgz", + "integrity": "sha512-fLnDML5HZ5AEKzHul8xLAksoKN2cibu6MgonkUj8R9V7bbeVRkd1XbGEGWrAUNYHbX1jcqCsDEpBviE5StPMzQ==", "dev": true, "requires": { "envinfo": "^7.7.3" } }, "@webpack-cli/serve": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.1.0.tgz", - "integrity": "sha512-7RfnMXCpJ/NThrhq4gYQYILB18xWyoQcBey81oIyVbmgbc6m5ZHHyFK+DyH7pLHJf0p14MxL4mTsoPAgBSTpIg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.1.tgz", + "integrity": "sha512-Zj1z6AyS+vqV6Hfi7ngCjFGdHV5EwZNIHo6QfFTNe9PyW+zBU1zJ9BiOW1pmUEq950RC4+Dym6flyA/61/vhyw==", "dev": true }, "@xtuc/ieee754": { @@ -2973,12 +2792,6 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, - "array-back": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz", - "integrity": "sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==", - "dev": true - }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -2996,59 +2809,6 @@ "es-abstract": "^1.18.0-next.1", "get-intrinsic": "^1.0.1", "is-string": "^1.0.5" - }, - "dependencies": { - "es-abstract": { - "version": "1.18.0-next.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz", - "integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.1", - "object-inspect": "^1.9.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.3", - "string.prototype.trimstart": "^1.0.3" - }, - "dependencies": { - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "get-intrinsic": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz", - "integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - } - } - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - } } }, "array-union": { @@ -3170,6 +2930,15 @@ "dev": true, "optional": true }, + "async-mutex": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.6.tgz", + "integrity": "sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw==", + "dev": true, + "requires": { + "tslib": "^2.0.0" + } + }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3897,16 +3666,16 @@ } }, "browserslist": { - "version": "4.16.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.0.tgz", - "integrity": "sha512-/j6k8R0p3nxOC6kx5JGAxsnhc9ixaWJfYc+TNTzxg6+ARaESAvQGV7h0uNOB4t+pLQJZWzcrMxXOxjgsCj3dqQ==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz", + "integrity": "sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001165", + "caniuse-lite": "^1.0.30001173", "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.621", + "electron-to-chromium": "^1.3.634", "escalade": "^3.1.1", - "node-releases": "^1.1.67" + "node-releases": "^1.1.69" } }, "bser": { @@ -3947,9 +3716,9 @@ "dev": true }, "bufferutil": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.2.tgz", - "integrity": "sha512-AtnG3W6M8B2n4xDQ5R+70EXvOpnXsFYg/AK2yTZd+HQ/oxAdz+GI+DvjmhBw3L0ole+LJ0ngqY4JMbDzkfNzhA==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz", + "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==", "optional": true, "requires": { "node-gyp-build": "^4.2.0" @@ -4000,11 +3769,6 @@ "requires": { "aggregate-error": "^3.0.0" } - }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } }, @@ -4026,13 +3790,13 @@ } }, "call-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", - "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", "dev": true, "requires": { "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.0" + "get-intrinsic": "^1.0.2" } }, "callsites": { @@ -4044,12 +3808,13 @@ "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true }, "caniuse-lite": { - "version": "1.0.30001168", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001168.tgz", - "integrity": "sha512-P2zmX7swIXKu+GMMR01TWa4csIKELTNnZKc+f1CjebmZJQtTAEXmpQSoKVJVVcvPGAA0TEYTOUp3VehavZSFPQ==", + "version": "1.0.30001178", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001178.tgz", + "integrity": "sha512-VtdZLC0vsXykKni8Uztx45xynytOi71Ufx9T8kHptSw9AL4dpqailUJJHavttuzUe1KYuBYtChiWv+BAb7mPmQ==", "dev": true }, "capture-exit": { @@ -4109,15 +3874,15 @@ "dev": true }, "chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "dev": true, "optional": true, "requires": { "anymatch": "~3.1.1", "braces": "~3.0.2", - "fsevents": "~2.1.2", + "fsevents": "~2.3.1", "glob-parent": "~5.1.0", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", @@ -4137,9 +3902,9 @@ } }, "binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true, "optional": true }, @@ -4413,18 +4178,6 @@ "delayed-stream": "~1.0.0" } }, - "command-line-usage": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.1.tgz", - "integrity": "sha512-F59pEuAR9o1SF/bD0dQBDluhpT4jJQNWUHEuVBqpDmCUo6gPjCi+m9fCWnWZVR/oG6cMTUms4h+3NPl74wGXvA==", - "dev": true, - "requires": { - "array-back": "^4.0.1", - "chalk": "^2.4.2", - "table-layout": "^1.0.1", - "typical": "^5.2.0" - } - }, "commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -4610,17 +4363,17 @@ "dev": true }, "core-js": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.1.tgz", - "integrity": "sha512-9Id2xHY1W7m8hCl8NkhQn5CufmF/WuR30BTRewvCXc1aZd3kMECwNZ69ndLbekKfakw9Rf2Xyc+QR6E7Gg+obg==" + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.2.tgz", + "integrity": "sha512-FfApuSRgrR6G5s58casCBd9M2k+4ikuu4wbW6pJyYU7bd9zvFc9qf7vr5xmrZOhT9nn+8uwlH1oRR9jTnFoA3A==" }, "core-js-compat": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.1.tgz", - "integrity": "sha512-a16TLmy9NVD1rkjUGbwuyWkiDoN0FDpAwrfLONvHFQx0D9k7J9y0srwMT8QP/Z6HE3MIFaVynEeYwZwPX1o5RQ==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.2.tgz", + "integrity": "sha512-LO8uL9lOIyRRrQmZxHZFl1RV+ZbcsAkFWTktn5SmH40WgLtSNYN4m4W2v9ONT147PxBY/XrRhrWq8TlvObyUjQ==", "dev": true, "requires": { - "browserslist": "^4.15.0", + "browserslist": "^4.16.0", "semver": "7.0.0" }, "dependencies": { @@ -4633,9 +4386,9 @@ } }, "core-js-pure": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.1.tgz", - "integrity": "sha512-Se+LaxqXlVXGvmexKGPvnUIYC1jwXu1H6Pkyb3uBM5d8/NELMYCHs/4/roD7721NxrTLyv7e5nXd5/QLBO+10g==" + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.2.tgz", + "integrity": "sha512-v6zfIQqL/pzTVAbZvYUozsxNfxcFb6Ks3ZfEbuneJl3FW9Jb8F6vLWB6f+qTmAu72msUdyb84V8d/yBFf7FNnw==" }, "core-util-is": { "version": "1.0.2", @@ -4785,7 +4538,8 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decimal.js": { "version": "10.2.1", @@ -4822,12 +4576,6 @@ } } }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true - }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -5160,9 +4908,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.629", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.629.tgz", - "integrity": "sha512-iSPPJtPvHrMAvYOt+9cdbDmTasPqwnwz4lkP8Dn200gDNUBQOLQ96xUsWXBwXslAo5XxdoXAoQQ3RAy4uao9IQ==", + "version": "1.3.641", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.641.tgz", + "integrity": "sha512-b0DLhsHSHESC1I+Nx6n4w4Lr61chMd3m/av1rZQhS2IXTzaS5BMM5N+ldWdMIlni9CITMRM09m8He4+YV/92TA==", "dev": true }, "elliptic": { @@ -5212,6 +4960,59 @@ "once": "^1.4.0" } }, + "enhanced-resolve": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", + "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "memory-fs": "^0.5.0", + "tapable": "^1.0.0" + }, + "dependencies": { + "memory-fs": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", + "dev": true, + "requires": { + "errno": "^0.1.3", + "readable-stream": "^2.0.1" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", @@ -5246,23 +5047,25 @@ } }, "es-abstract": { - "version": "1.18.0-next.1", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.1.tgz", - "integrity": "sha512-I4UGspA0wpZXWENrdA0uHbnhte683t3qT/1VFH9aX2dA5PPSf6QW5HHXf5HImaqPmjXaVeVk4RGWnaylmV7uAA==", + "version": "1.18.0-next.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz", + "integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==", "dev": true, "requires": { + "call-bind": "^1.0.2", "es-to-primitive": "^1.2.1", "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2", "has": "^1.0.3", "has-symbols": "^1.0.1", "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.0", + "is-negative-zero": "^2.0.1", "is-regex": "^1.1.1", - "object-inspect": "^1.8.0", + "object-inspect": "^1.9.0", "object-keys": "^1.1.1", - "object.assign": "^4.1.1", - "string.prototype.trimend": "^1.0.1", - "string.prototype.trimstart": "^1.0.1" + "object.assign": "^4.1.2", + "string.prototype.trimend": "^1.0.3", + "string.prototype.trimstart": "^1.0.3" } }, "es-to-primitive": { @@ -5371,13 +5174,13 @@ } }, "eslint": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.16.0.tgz", - "integrity": "sha512-iVWPS785RuDA4dWuhhgXTNrGxHHK3a8HLSMBgbbU59ruJDubUraXN8N5rn7kb8tG6sjg74eE0RA3YWT51eusEw==", + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", + "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.2.2", + "@eslint/eslintrc": "^0.3.0", "ajv": "^6.10.0", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -5401,7 +5204,7 @@ "js-yaml": "^3.13.1", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", - "lodash": "^4.17.19", + "lodash": "^4.17.20", "minimatch": "^3.0.4", "natural-compare": "^1.4.0", "optionator": "^0.9.1", @@ -5835,40 +5638,40 @@ "dev": true }, "ethers": { - "version": "5.0.24", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.0.24.tgz", - "integrity": "sha512-77CEtVC88fJGEhxGXRvQqAEH6e2A+ZFiv2FBT6ikXndlty5sw6vMatAhg1v+w3CaaGZOf1CP81jl4Mc8Zrj08A==", - "requires": { - "@ethersproject/abi": "5.0.9", - "@ethersproject/abstract-provider": "5.0.7", - "@ethersproject/abstract-signer": "5.0.9", - "@ethersproject/address": "5.0.8", - "@ethersproject/base64": "5.0.6", - "@ethersproject/basex": "5.0.6", - "@ethersproject/bignumber": "5.0.12", - "@ethersproject/bytes": "5.0.8", - "@ethersproject/constants": "5.0.7", - "@ethersproject/contracts": "5.0.8", - "@ethersproject/hash": "5.0.9", - "@ethersproject/hdnode": "5.0.7", - "@ethersproject/json-wallets": "5.0.9", - "@ethersproject/keccak256": "5.0.6", + "version": "5.0.26", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.0.26.tgz", + "integrity": "sha512-MqA8Fvutn3qEW0yBJOHeV6KZmRpF2rqlL2B5058AGkUFsuu6j5Ns/FRlMsbGeQwBz801IB23jQp7vjRfFsKSkg==", + "requires": { + "@ethersproject/abi": "5.0.10", + "@ethersproject/abstract-provider": "5.0.8", + "@ethersproject/abstract-signer": "5.0.11", + "@ethersproject/address": "5.0.9", + "@ethersproject/base64": "5.0.7", + "@ethersproject/basex": "5.0.7", + "@ethersproject/bignumber": "5.0.13", + "@ethersproject/bytes": "5.0.9", + "@ethersproject/constants": "5.0.8", + "@ethersproject/contracts": "5.0.9", + "@ethersproject/hash": "5.0.10", + "@ethersproject/hdnode": "5.0.8", + "@ethersproject/json-wallets": "5.0.10", + "@ethersproject/keccak256": "5.0.7", "@ethersproject/logger": "5.0.8", - "@ethersproject/networks": "5.0.6", - "@ethersproject/pbkdf2": "5.0.6", - "@ethersproject/properties": "5.0.6", - "@ethersproject/providers": "5.0.17", - "@ethersproject/random": "5.0.6", - "@ethersproject/rlp": "5.0.6", - "@ethersproject/sha2": "5.0.6", - "@ethersproject/signing-key": "5.0.7", - "@ethersproject/solidity": "5.0.7", - "@ethersproject/strings": "5.0.7", - "@ethersproject/transactions": "5.0.8", - "@ethersproject/units": "5.0.8", - "@ethersproject/wallet": "5.0.9", - "@ethersproject/web": "5.0.11", - "@ethersproject/wordlists": "5.0.7" + "@ethersproject/networks": "5.0.7", + "@ethersproject/pbkdf2": "5.0.7", + "@ethersproject/properties": "5.0.7", + "@ethersproject/providers": "5.0.19", + "@ethersproject/random": "5.0.7", + "@ethersproject/rlp": "5.0.7", + "@ethersproject/sha2": "5.0.7", + "@ethersproject/signing-key": "5.0.8", + "@ethersproject/solidity": "5.0.8", + "@ethersproject/strings": "5.0.8", + "@ethersproject/transactions": "5.0.9", + "@ethersproject/units": "5.0.9", + "@ethersproject/wallet": "5.0.10", + "@ethersproject/web": "5.0.12", + "@ethersproject/wordlists": "5.0.8" } }, "eventemitter3": { @@ -6326,6 +6129,12 @@ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true + }, "fastq": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", @@ -6731,9 +6540,9 @@ "dev": true }, "fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", + "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", "dev": true, "optional": true }, @@ -7326,9 +7135,9 @@ "dev": true }, "import-fresh": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", - "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -8752,9 +8561,9 @@ "dev": true }, "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", @@ -9936,18 +9745,18 @@ "dev": true }, "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", + "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", "dev": true }, "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "version": "2.1.28", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", + "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", "dev": true, "requires": { - "mime-db": "1.44.0" + "mime-db": "1.45.0" } }, "mimic-fn": { @@ -10661,9 +10470,9 @@ } }, "node-releases": { - "version": "1.1.67", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.67.tgz", - "integrity": "sha512-V5QF9noGFl3EymEwUYzO+3NTDpGfQB4ve6Qfnzf3UNydMhjQRVPR1DZTuvWiLzaFJYw2fmDwAfnRNEVb64hSIg==", + "version": "1.1.69", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.69.tgz", + "integrity": "sha512-DGIjo79VDEyAnRlfSqYTsy+yoHd2IOjJiKUozD2MV2D85Vso6Bug56mb9tT/fY5Urt0iqk01H7x+llAruDR2zA==", "dev": true }, "node-status-codes": { @@ -10817,59 +10626,6 @@ "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.1", "has": "^1.0.3" - }, - "dependencies": { - "es-abstract": { - "version": "1.18.0-next.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz", - "integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.1", - "object-inspect": "^1.9.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.3", - "string.prototype.trimstart": "^1.0.3" - }, - "dependencies": { - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - } - } - }, - "get-intrinsic": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz", - "integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - } } }, "object.getownpropertydescriptors": { @@ -10902,59 +10658,6 @@ "define-properties": "^1.1.3", "es-abstract": "^1.18.0-next.1", "has": "^1.0.3" - }, - "dependencies": { - "es-abstract": { - "version": "1.18.0-next.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.18.0-next.2.tgz", - "integrity": "sha512-Ih4ZMFHEtZupnUh6497zEL4y2+w8+1ljnCyaTa+adcoafI1GOvMwFlDjBLfWR7y9VLfrjRJe9ocuHY1PSR9jjw==", - "dev": true, - "requires": { - "call-bind": "^1.0.2", - "es-to-primitive": "^1.2.1", - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2", - "has": "^1.0.3", - "has-symbols": "^1.0.1", - "is-callable": "^1.2.2", - "is-negative-zero": "^2.0.1", - "is-regex": "^1.1.1", - "object-inspect": "^1.9.0", - "object-keys": "^1.1.1", - "object.assign": "^4.1.2", - "string.prototype.trimend": "^1.0.3", - "string.prototype.trimstart": "^1.0.3" - }, - "dependencies": { - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - } - } - }, - "get-intrinsic": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz", - "integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "is-negative-zero": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.1.tgz", - "integrity": "sha512-2z6JzQvZRa9A2Y7xC6dQQm4FSTSTNWjKIYYTt4246eMTJmIo0Q+ZyOsU66X8lxK1AbB92dFeglPLrhwpeRKO6w==", - "dev": true - } } }, "on-finished": { @@ -12060,12 +11763,6 @@ "resolve": "^1.9.0" } }, - "reduce-flatten": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", - "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", - "dev": true - }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -12280,6 +11977,12 @@ "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", "dev": true }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -12640,14 +12343,13 @@ "dev": true }, "sinon": { - "version": "9.2.2", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.2.tgz", - "integrity": "sha512-9Owi+RisvCZpB0bdOVFfL314I6I4YoRlz6Isi4+fr8q8YQsDPoCe5UnmNtKHRThX3negz2bXHWIuiPa42vM8EQ==", + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.3.tgz", + "integrity": "sha512-m+DyAWvqVHZtjnjX/nuShasykFeiZ+nPuEfD4G3gpvKGkXRhkF/6NSt2qN2FjZhfrcHXFzUzI+NLnk+42fnLEw==", "dev": true, "requires": { "@sinonjs/commons": "^1.8.1", "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/formatio": "^5.0.1", "@sinonjs/samsam": "^5.3.0", "diff": "^4.0.2", "nise": "^4.0.4", @@ -12689,9 +12391,9 @@ }, "dependencies": { "mime": { - "version": "2.4.7", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.7.tgz", - "integrity": "sha512-dhNd1uA2u397uQk3Nv5LM4lm93WYDUXFn3Fu291FJerns4jyTudqhIWe4W04YLy7Uk1tm1Ore04NpjRvQp/NPA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.0.tgz", + "integrity": "sha512-ft3WayFSFUVBuJj7BMLKAQcSlItKtfjsKDDsii3rqFDAZ7t11zRe8ASw/GlmivGwVUYtwkQrxiGGpL6gFvB0ag==", "dev": true } } @@ -13196,9 +12898,9 @@ } }, "streamr-test-utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/streamr-test-utils/-/streamr-test-utils-1.2.0.tgz", - "integrity": "sha512-oVs85qoxO7mehPPj+0SETVu5LrMvCeTFiJuTK9P47uRrZ2NJjflGQgDULJbGbCsg4csJ1K9j/7Jxi7ve/qBvWA==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/streamr-test-utils/-/streamr-test-utils-1.2.1.tgz", + "integrity": "sha512-rQYAxqZ+LNuPlbZSUV7yuu0innb5BeERvEHsyRuxaYIGRyj0qq6u896IyNeH3Lgdk1A1fGUf6OCgW4/EFnJA2g==", "dev": true }, "string-length": { @@ -13360,27 +13062,35 @@ "dev": true }, "table": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/table/-/table-6.0.4.tgz", - "integrity": "sha512-sBT4xRLdALd+NFBvwOz8bw4b15htyythha+q+DVZqy2RS08PPC8O2sZFgJYEY7bJvbCFKccs+WIZ/cd+xxTWCw==", + "version": "6.0.7", + "resolved": "https://registry.npmjs.org/table/-/table-6.0.7.tgz", + "integrity": "sha512-rxZevLGTUzWna/qBLObOe16kB2RTnnbhciwgPbMMlazz1yZGVEgnZK762xyVdVznhqxrfCeBMmMkgOOaPwjH7g==", "dev": true, "requires": { - "ajv": "^6.12.4", + "ajv": "^7.0.2", "lodash": "^4.17.20", "slice-ansi": "^4.0.0", "string-width": "^4.2.0" - } - }, - "table-layout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.1.tgz", - "integrity": "sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==", - "dev": true, - "requires": { - "array-back": "^4.0.1", - "deep-extend": "~0.6.0", - "typical": "^5.2.0", - "wordwrapjs": "^4.0.0" + }, + "dependencies": { + "ajv": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz", + "integrity": "sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } } }, "tapable": { @@ -13476,11 +13186,6 @@ "ajv-keywords": "^3.5.2" } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - }, "source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -13699,9 +13404,9 @@ } }, "tslib": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.0.3.tgz", - "integrity": "sha512-uZtkfKblCEQtZKBF6EBXVZeQNl82yqtDQdv+eck8u7tdPxjLu2/lp5/uPW+um2tpuxINHWy3GhiccY7QgEaVHQ==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" }, "tty-browserify": { "version": "0.0.0", @@ -13770,12 +13475,6 @@ "is-typedarray": "^1.0.0" } }, - "typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true - }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", @@ -13900,9 +13599,9 @@ "optional": true }, "uri-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", - "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "requires": { "punycode": "^2.1.0" @@ -13948,9 +13647,9 @@ "dev": true }, "utf-8-validate": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.3.tgz", - "integrity": "sha512-jtJM6fpGv8C1SoH4PtG22pGto6x+Y8uPprW0tw3//gGFhDDTiuksgradgFN6yRayDP4SyZZa6ZMGHLIa17+M8A==", + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.4.tgz", + "integrity": "sha512-MEF05cPSq3AwJ2C7B7sHAA6i53vONoZbMGX8My5auEVm6W+dJ2Jd/TZPyGJ5CH42V2XtbI5FD28HeHeqlPzZ3Q==", "optional": true, "requires": { "node-gyp-build": "^4.2.0" @@ -13996,9 +13695,9 @@ "dev": true }, "v8-to-istanbul": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.0.0.tgz", - "integrity": "sha512-fLL2rFuQpMtm9r8hrAV2apXX/WqHJ6+IC4/eQVdMDGBUgH/YMV4Gv3duk3kjmyg6uiQWBAA9nJwue4iJUOkHeA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz", + "integrity": "sha512-uXUVqNUCLa0AH1vuVxzi+MI4RfxEOKt9pBgKwHbgH7st8Kv2P1m+jvWNnektzBh5QShF3ODgKmUFCf38LnVz1g==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.1", @@ -14158,9 +13857,9 @@ "dev": true }, "webpack": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", - "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", + "version": "4.46.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", + "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", "dev": true, "requires": { "@webassemblyjs/ast": "1.9.0", @@ -14171,7 +13870,7 @@ "ajv": "^6.10.2", "ajv-keywords": "^3.4.1", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.3.0", + "enhanced-resolve": "^4.5.0", "eslint-scope": "^4.0.3", "json-parse-better-errors": "^1.0.2", "loader-runner": "^2.4.0", @@ -14229,29 +13928,6 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, - "enhanced-resolve": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.5.0.tgz", - "integrity": "sha512-Nv9m36S/vxpsI+Hc4/ZGRs0n9mXqSWGGq49zxb/cJfPAQMbUtttJAlNPS4AQzaBdw/pKskw5bMbekT/Y7W/Wlg==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "dependencies": { - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - } - } - }, "eslint-scope": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", @@ -14349,21 +14025,6 @@ "find-up": "^3.0.0" } }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, "rimraf": { "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", @@ -14373,12 +14034,6 @@ "glob": "^7.1.3" } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, "schema-utils": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", @@ -14414,15 +14069,6 @@ "figgy-pudding": "^3.5.1" } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, "terser": { "version": "4.8.0", "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", @@ -14483,9 +14129,9 @@ "dev": true }, "acorn-walk": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.0.tgz", - "integrity": "sha512-oZRad/3SMOI/pxbbmqyurIx7jHw1wZDcR9G44L8pUVFEomX/0dH89SrM1KaDXuv1NpzAXz6Op/Xu/Qd5XXzdEA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.1.tgz", + "integrity": "sha512-zn/7dYtoTVkG4EoMU55QlQU4F+m+T7Kren6Vj3C2DapWPnakG/DL9Ns5aPAPW5Ixd3uxXrV/BoMKKVFIazPcdg==", "dev": true }, "ansi-styles": { @@ -14546,21 +14192,21 @@ } }, "webpack-cli": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.2.0.tgz", - "integrity": "sha512-EIl3k88vaF4fSxWSgtAQR+VwicfLMTZ9amQtqS4o+TDPW9HGaEpbFBbAZ4A3ZOT5SOnMxNOzROsSTPiE8tBJPA==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.3.1.tgz", + "integrity": "sha512-/F4+9QNZM/qKzzL9/06Am8NXIkGV+/NqQ62Dx7DSqudxxpAgBqYn6V7+zp+0Y7JuWksKUbczRY3wMTd+7Uj6OA==", "dev": true, "requires": { - "@webpack-cli/info": "^1.1.0", - "@webpack-cli/serve": "^1.1.0", + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/info": "^1.2.1", + "@webpack-cli/serve": "^1.2.1", "colorette": "^1.2.1", - "command-line-usage": "^6.1.0", "commander": "^6.2.0", "enquirer": "^2.3.6", - "execa": "^4.1.0", + "execa": "^5.0.0", + "fastest-levenshtein": "^1.0.12", "import-local": "^3.0.2", "interpret": "^2.2.0", - "leven": "^3.1.0", "rechoir": "^0.7.0", "v8-compile-cache": "^2.2.0", "webpack-merge": "^4.2.2" @@ -14573,22 +14219,34 @@ "dev": true }, "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.0.0.tgz", + "integrity": "sha512-ov6w/2LCiuyO4RLYGdpFGjkcs0wMTgGE8PrkTHikeUy5iJekXyPIKUjifk5CsE0pt7sMCrMZ3YNqoCj6idQOnQ==", "dev": true, "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, + "get-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.0.tgz", + "integrity": "sha512-A1B3Bh1UmL0bidM/YX2NsCOTnGJePL9rO/M+Mw3m9f2gUpfokS0hi5Eah0WSUEWZdZhIZtMjkIYS7mDfOqNHbg==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, "is-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", @@ -14612,22 +14270,13 @@ "requires": { "lodash": "^4.17.15" } - }, - "yargs-parser": { - "version": "13.1.2", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", - "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } } } }, "webpack-merge": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.2.tgz", - "integrity": "sha512-7o7qjwcIB6lqHX0VZA2Vxcp8RHftW1LNcaB6t87PEpco/VPlG0Wn9DnvgmcJ0nZU578/vKQfhDSLTF0EZ+pFAg==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz", + "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==", "dev": true, "requires": { "clone-deep": "^4.0.1", @@ -14752,16 +14401,6 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "wordwrapjs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.0.tgz", - "integrity": "sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ==", - "dev": true, - "requires": { - "reduce-flatten": "^2.0.0", - "typical": "^5.0.0" - } - }, "worker-farm": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", @@ -14841,9 +14480,9 @@ } }, "ws": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", - "integrity": "sha512-pTsP8UAfhy3sk1lSk/O/s4tjD0CRwvMnzvwr4OKGX7ZvqZtUyx4KIJB5JWbkykPoc55tixMGgTNoh3k4FkNGFQ==" + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index e2ee918f5..883fc4fd5 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@babel/plugin-transform-modules-commonjs": "^7.12.1", "@babel/plugin-transform-runtime": "^7.12.1", "@babel/preset-env": "^7.12.7", + "async-mutex": "^0.2.4", "babel-eslint": "^10.1.0", "babel-loader": "^8.2.1", "babel-plugin-add-module-exports": "^1.0.4", @@ -78,6 +79,15 @@ "#IMPORTANT": "babel-runtime must be in dependencies, not devDependencies", "dependencies": { "@babel/runtime": "^7.12.5", + "@ethersproject/address": "^5.0.3", + "@ethersproject/bignumber": "^5.0.6", + "@ethersproject/bytes": "^5.0.3", + "@ethersproject/contracts": "^5.0.3", + "@ethersproject/keccak256": "^5.0.6", + "@ethersproject/providers": "^5.0.14", + "@ethersproject/sha2": "^5.0.3", + "@ethersproject/transactions": "^5.0.4", + "@ethersproject/wallet": "^5.0.6", "debug": "^4.3.1", "ethers": "^5.0.21", "eventemitter3": "^4.0.7", diff --git a/src/Config.js b/src/Config.js index 0db0240fd..0894bc733 100644 --- a/src/Config.js +++ b/src/Config.js @@ -9,28 +9,47 @@ const { StreamMessage } = MessageLayer export default function ClientConfig(opts = {}) { const { id = counterId('StreamrClient') } = opts - // Default options + const options = { debug: Debug(id), - // The server to connect to - url: 'wss://streamr.network/api/v1/ws', - restUrl: 'https://streamr.network/api/v1', - // Automatically connect on first subscribe - autoConnect: true, - // Automatically disconnect on last unsubscribe - autoDisconnect: true, + // Authentication: identity used by this StreamrClient instance + auth: {}, // can contain member privateKey or (window.)ethereum + + // Streamr Core options + url: 'wss://streamr.network/api/v1/ws', // The server to connect to + restUrl: 'https://streamr.network/api/v1', // Core API calls go here + streamrNodeAddress: '0xf3E5A65851C3779f468c9EcB32E6f25D9D68601a', // joinPartAgent when using EE for join part handling + + // P2P Streamr Network options + autoConnect: true, // Automatically connect on first subscribe + autoDisconnect: true, // Automatically disconnect on last unsubscribe orderMessages: true, - auth: {}, - groupKeys: {}, - publishWithSignature: 'auto', - verifySignatures: 'auto', retryResendAfter: 5000, gapFillTimeout: 5000, maxPublishQueueSize: 10000, - streamrNodeAddress: '0xf3E5A65851C3779f468c9EcB32E6f25D9D68601a', - streamrOperatorAddress: '0xc0aa4dC0763550161a6B59fa430361b5a26df28C', - tokenAddress: '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', + + // Encryption options + publishWithSignature: 'auto', + verifySignatures: 'auto', + publisherStoreKeyHistory: true, + groupKeys: {}, // {streamId: groupKey} keyExchange: {}, + + // Ethereum and Data Union related options + // For ethers.js provider params, see https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#provider + mainnet: null, // Default to ethers.js default provider settings + sidechain: { + url: null, // TODO: add our default public service sidechain node, also find good PoA params below + // timeout: + // pollingInterval: + }, + dataUnion: null, // Give a "default target" of all data union endpoint operations (no need to pass argument every time) + tokenAddress: '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', + minimumWithdrawTokenWei: '1000000', // Threshold value set in AMB configs, smallest token amount to pass over the bridge + sidechainTokenAddress: null, // TODO // sidechain token + factoryMainnetAddress: null, // TODO // Data Union factory that creates a new Data Union + sidechainAmbAddress: null, // Arbitrary Message-passing Bridge (AMB), see https://github.com/poanetwork/tokenbridge + payForSignatureTransport: true, // someone must pay for transporting the withdraw tx to mainnet, either us or bridge operator ...opts, cache: { maxSize: 10000, diff --git a/src/Session.js b/src/Session.js index d862108d5..624dca32d 100644 --- a/src/Session.js +++ b/src/Session.js @@ -1,5 +1,6 @@ import EventEmitter from 'eventemitter3' -import { ethers } from 'ethers' +import { Wallet } from '@ethersproject/wallet' +import { Web3Provider } from '@ethersproject/providers' export default class Session extends EventEmitter { constructor(client, options) { @@ -8,11 +9,12 @@ export default class Session extends EventEmitter { this.options = options || {} this.state = Session.State.LOGGED_OUT + // TODO: move loginFunction to StreamrClient constructor where "auth type" is checked if (typeof this.options.privateKey !== 'undefined') { - const wallet = new ethers.Wallet(this.options.privateKey) + const wallet = new Wallet(this.options.privateKey) this.loginFunction = async () => this._client.loginWithChallengeResponse((d) => wallet.signMessage(d), wallet.address) - } else if (typeof this.options.provider !== 'undefined') { - const provider = new ethers.providers.Web3Provider(this.options.provider) + } else if (typeof this.options.ethereum !== 'undefined') { + const provider = new Web3Provider(this.options.ethereum) const signer = provider.getSigner() this.loginFunction = async () => this._client.loginWithChallengeResponse((d) => signer.signMessage(d), await signer.getAddress()) } else if (typeof this.options.apiKey !== 'undefined') { @@ -24,7 +26,7 @@ export default class Session extends EventEmitter { this.options.unauthenticated = true } this.loginFunction = async () => { - throw new Error('Need either "privateKey", "provider", "apiKey", "username"+"password" or "sessionToken" to login.') + throw new Error('Need either "privateKey", "ethereum", "apiKey", "username"+"password" or "sessionToken" to login.') } } } diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 5b0f75377..07679d078 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -1,5 +1,6 @@ import EventEmitter from 'eventemitter3' -import { Wallet } from 'ethers' +import { Wallet } from '@ethersproject/wallet' +import { getDefaultProvider, providers } from 'ethers' import { ControlLayer } from 'streamr-client-protocol' import Debug from 'debug' @@ -11,6 +12,11 @@ import Connection from './Connection' import Publisher from './publish' import Subscriber from './subscribe' +const { + JsonRpcProvider, + Web3Provider +} = providers + /** * Wrap connection message events with message parsing. */ @@ -92,7 +98,7 @@ class StreamrCached { const uid = process.pid != null ? process.pid : `${uuid().slice(-4)}${uuid().slice(0, 4)}` export default class StreamrClient extends EventEmitter { - constructor(options, connection) { + constructor(options = {}, connection) { super() this.id = counterId(`${this.constructor.name}:${uid}`) this.debug = Debug(this.id) @@ -164,10 +170,25 @@ export default class StreamrClient extends EventEmitter { return this.connection.send(request) } + /** @returns Ethers.js Provider, a connection to the Ethereum network (mainnet) */ + getMainnetProvider() { + if (this.options.mainnet) { + return new JsonRpcProvider(this.options.mainnet) + } + return getDefaultProvider() + } + + /** @returns Ethers.js Provider, a connection to the Streamr EVM sidechain */ + getSidechainProvider() { + if (this.options.sidechain) { + return new JsonRpcProvider(this.options.sidechain) + } + return null + } + /** * Override to control output */ - onError(error) { // eslint-disable-line class-methods-use-this console.error(error) } diff --git a/src/publish/Signer.js b/src/publish/Signer.js index 489b12dee..0f0061254 100644 --- a/src/publish/Signer.js +++ b/src/publish/Signer.js @@ -1,18 +1,19 @@ import { MessageLayer, Utils } from 'streamr-client-protocol' -import { ethers } from 'ethers' +import { computeAddress } from '@ethersproject/transactions' +import { Web3Provider } from '@ethersproject/providers' const { StreamMessage } = MessageLayer const { SigningUtil } = Utils const { SIGNATURE_TYPES } = StreamMessage export default function Signer(options = {}, publishWithSignature = 'auto') { - const { privateKey, provider } = options + const { privateKey, ethereum } = options if (publishWithSignature === 'never') { return (v) => v } - if (publishWithSignature === 'auto' && !privateKey && !provider) { + if (publishWithSignature === 'auto' && !privateKey && !ethereum) { return (v) => v } @@ -22,18 +23,25 @@ export default function Signer(options = {}, publishWithSignature = 'auto') { let address let sign + let getAddress = async () => {} if (privateKey) { - address = ethers.utils.computeAddress(privateKey) + address = computeAddress(privateKey) const key = (typeof privateKey === 'string' && privateKey.startsWith('0x')) ? privateKey.slice(2) // strip leading 0x : privateKey + getAddress = async () => address sign = async (d) => { return SigningUtil.sign(d, key) } - } else if (provider) { - const web3Provider = new ethers.providers.Web3Provider(provider) + } else if (ethereum) { + const web3Provider = new Web3Provider(ethereum) const signer = web3Provider.getSigner() - address = signer.address + getAddress = async () => { + if (address) { return address } + // eslint-disable-next-line require-atomic-updates + address = await signer.getAddress() + return address + } sign = async (d) => signer.signMessage(d) } else { throw new Error('Need either "privateKey" or "provider".') @@ -63,8 +71,6 @@ export default function Signer(options = {}, publishWithSignature = 'auto') { return Object.assign(signStreamMessage, { // these mainly for tests signData: sign, - get address() { - return address - } + getAddress, }) } diff --git a/src/publish/index.js b/src/publish/index.js index b90f3fc0c..472abea41 100644 --- a/src/publish/index.js +++ b/src/publish/index.js @@ -2,7 +2,10 @@ import { inspect } from 'util' import crypto from 'crypto' import { ControlLayer, MessageLayer } from 'streamr-client-protocol' -import { ethers } from 'ethers' +import { hexlify } from '@ethersproject/bytes' +import { computeAddress } from '@ethersproject/transactions' +import { Web3Provider } from '@ethersproject/providers' +import { sha256 } from '@ethersproject/sha2' import mem from 'mem' import { uuid, CacheFn, LimitAsyncFnByKey, randomString } from '../utils' @@ -126,22 +129,23 @@ async function getPublisherId(client) { const { options: { auth = {} } = {} } = client if (auth.privateKey) { - return ethers.utils.computeAddress(auth.privateKey).toLowerCase() + return computeAddress(auth.privateKey).toLowerCase() } if (auth.provider) { - const provider = new ethers.providers.Web3Provider(auth.provider) - return provider.getSigner().address.toLowerCase() + const provider = new Web3Provider(auth.ethereum) + const address = (await provider.getSigner().getAddress()).toLowerCase() + return address } const username = await getUsername(client) if (username != null) { - const hexString = ethers.utils.hexlify(Buffer.from(username, 'utf8')) - return ethers.utils.sha256(hexString) + const hexString = hexlify(Buffer.from(await this.getUsername(), 'utf8')) + return sha256(hexString) } - throw new Error('Need either "privateKey", "provider", "apiKey", "username"+"password" or "sessionToken" to derive the publisher Id.') + throw new Error('Need either "privateKey", "ethereum", "apiKey", "username"+"password" or "sessionToken" to derive the publisher Id.') } /* diff --git a/src/rest/DataUnionEndpoints.js b/src/rest/DataUnionEndpoints.js index 73e14c1ae..eda679422 100644 --- a/src/rest/DataUnionEndpoints.js +++ b/src/rest/DataUnionEndpoints.js @@ -2,32 +2,260 @@ * Streamr Data Union related functions * * Table of Contents: - * member: JOIN & QUERY DATA UNION Publicly available info about dataunions and their members (with earnings and proofs) - * member: WITHDRAW EARNINGS Withdrawing functions, there's many: normal, agent, donate + * ABIs + * helper utils * admin: DEPLOY AND SETUP DATA UNION Functions for deploying the contract and adding secrets for smooth joining * admin: MANAGE DATA UNION Kick and add members + * member: JOIN & QUERY DATA UNION Publicly available info about dataunions and their members (with earnings and proofs) + * member: WITHDRAW EARNINGS Withdrawing functions, there's many: normal, agent, donate */ -import fetch from 'node-fetch' -import { - Contract, - ContractFactory, - Wallet, - getDefaultProvider, - providers, - BigNumber, - utils as ethersUtils, -} from 'ethers' -import debugFactory from 'debug' +import { getAddress, isAddress } from '@ethersproject/address' +import { BigNumber } from '@ethersproject/bignumber' +import { arrayify, hexZeroPad } from '@ethersproject/bytes' +import { Contract } from '@ethersproject/contracts' +import { keccak256 } from '@ethersproject/keccak256' +import { verifyMessage } from '@ethersproject/wallet' +import debug from 'debug' + +import { until, getEndpointUrl } from '../utils' + +import authFetch from './authFetch' + +const log = debug('StreamrClient::DataUnionEndpoints') +// const log = console.log // useful for debugging sometimes + +// /////////////////////////////////////////////////////////////////////// +// ABIs: contract functions we want to call within the client +// /////////////////////////////////////////////////////////////////////// -import * as DataUnion from '../../contracts/DataUnion.json' -import { getEndpointUrl } from '../utils' +const dataUnionMainnetABI = [{ + name: 'sendTokensToBridge', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'token', + inputs: [], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'owner', + inputs: [], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'setAdminFee', + inputs: [{ type: 'uint256' }], + outputs: [], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'adminFeeFraction', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}] -import authFetch, { DEFAULT_HEADERS } from './authFetch' +const dataUnionSidechainABI = [{ + name: 'addMembers', + inputs: [{ type: 'address[]', internalType: 'address payable[]', }], + outputs: [], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'partMembers', + inputs: [{ type: 'address[]' }], + outputs: [], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'withdrawAll', + inputs: [{ type: 'address' }, { type: 'bool' }], + outputs: [{ type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'withdrawAllTo', + inputs: [{ type: 'address' }, { type: 'bool' }], + outputs: [{ type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'withdrawAllToSigned', + inputs: [{ type: 'address' }, { type: 'address' }, { type: 'bool' }, { type: 'bytes' }], + outputs: [{ type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function' +}, { + // enum ActiveStatus {None, Active, Inactive, Blocked} + // struct MemberInfo { + // ActiveStatus status; + // uint256 earnings_before_last_join; + // uint256 lme_at_join; + // uint256 withdrawnEarnings; + // } + name: 'memberData', + inputs: [{ type: 'address' }], + outputs: [{ type: 'uint8' }, { type: 'uint256' }, { type: 'uint256' }, { type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + inputs: [], + name: 'getStats', + outputs: [{ type: 'uint256[6]' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'getEarnings', + inputs: [{ type: 'address' }], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'getWithdrawableEarnings', + inputs: [{ type: 'address' }], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'lifetimeMemberEarnings', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'totalWithdrawable', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'totalEarnings', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'activeMemberCount', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + // this event is emitted by withdrawing process, + // see https://github.com/poanetwork/tokenbridge-contracts/blob/master/contracts/upgradeable_contracts/arbitrary_message/HomeAMB.sol + name: 'UserRequestForSignature', + inputs: [ + { indexed: true, name: 'messageId', type: 'bytes32' }, + { indexed: false, name: 'encodedData', type: 'bytes' } + ], + anonymous: false, + type: 'event' +}] -const { computeAddress, getAddress } = ethersUtils +// Only the part of ABI that is needed by deployment (and address resolution) +const factoryMainnetABI = [{ + type: 'constructor', + inputs: [{ type: 'address' }, { type: 'address' }, { type: 'address' }, { type: 'address' }, { type: 'uint256' }], + stateMutability: 'nonpayable' +}, { + name: 'sidechainAddress', + inputs: [{ type: 'address' }], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'mainnetAddress', + inputs: [{ type: 'address' }, { type: 'string' }], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'deployNewDataUnion', + inputs: [{ type: 'address' }, { type: 'uint256' }, { type: 'address[]' }, { type: 'string' }], + outputs: [{ type: 'address' }], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'amb', + inputs: [], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'data_union_sidechain_factory', + inputs: [], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}] -const debug = debugFactory('StreamrClient::DataUnionEndpoints') +const mainnetAmbABI = [{ + name: 'executeSignatures', + inputs: [{ type: 'bytes' }, { type: 'bytes' }], // data, signatures + outputs: [], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'messageCallStatus', + inputs: [{ type: 'bytes32' }], // messageId + outputs: [{ type: 'bool' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'failedMessageSender', + inputs: [{ type: 'bytes32' }], // messageId + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'relayedMessages', + inputs: [{ type: 'bytes32' }], // messageId, was called "_txhash" though?! + outputs: [{ name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'validatorContract', + inputs: [], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}] + +const sidechainAmbABI = [{ + name: 'signature', + inputs: [{ type: 'bytes32' }, { type: 'uint256' }], // messageHash, index + outputs: [{ type: 'bytes' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'message', + inputs: [{ type: 'bytes32' }], // messageHash + outputs: [{ type: 'bytes' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'requiredSignatures', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'numMessagesSigned', + inputs: [{ type: 'bytes32' }], // messageHash (TODO: double check) + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}] + +// ////////////////////////////////////////////////////////////////// +// Contract utils +// ////////////////////////////////////////////////////////////////// /** @typedef {String} EthereumAddress */ @@ -39,163 +267,398 @@ function throwIfBadAddress(address, variableDescription) { } } -async function throwIfNotContract(eth, address, variableDescription) { - const addr = throwIfBadAddress(address, variableDescription) - if (await eth.getCode(address) === '0x') { - throw new Error(`${variableDescription || 'Error'}: No contract at ${address}`) +/** + * Parse address, or use this client's auth address if input not given + * @param {StreamrClient} this + * @param {EthereumAddress} inputAddress from user (NOT case sensitive) + * @returns {EthereumAddress} with checksum case + */ +function parseAddress(client, inputAddress) { + if (isAddress(inputAddress)) { + return getAddress(inputAddress) } - return addr + return client.getAddress() } -function sleep(ms) { - return new Promise((resolve) => { - setTimeout(resolve, ms) - }) +// Find the Asyncronous Message-passing Bridge sidechain ("home") contract +let cachedSidechainAmb +async function getSidechainAmb(client, options) { + if (!cachedSidechainAmb) { + const getAmbPromise = async () => { + const mainnetProvider = client.getMainnetProvider() + const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress + const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, mainnetProvider) + const sidechainProvider = client.getSidechainProvider() + const factorySidechainAddress = await factoryMainnet.data_union_sidechain_factory() + const factorySidechain = new Contract(factorySidechainAddress, [{ + name: 'amb', + inputs: [], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' + }], sidechainProvider) + const sidechainAmbAddress = await factorySidechain.amb() + return new Contract(sidechainAmbAddress, sidechainAmbABI, sidechainProvider) + } + cachedSidechainAmb = getAmbPromise() + cachedSidechainAmb = await cachedSidechainAmb // eslint-disable-line require-atomic-updates + } + return cachedSidechainAmb } -async function get(client, dataUnionContractAddress, endpoint, opts = {}) { - const url = getEndpointUrl(client.options.restUrl, 'communities', dataUnionContractAddress) + endpoint - const response = await fetch(url, { - ...opts, - headers: { - ...DEFAULT_HEADERS, - ...opts.headers, - }, +async function getMainnetAmb(client, options) { + const mainnetProvider = client.getMainnetProvider() + const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress + const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, mainnetProvider) + const mainnetAmbAddress = await factoryMainnet.amb() + return new Contract(mainnetAmbAddress, mainnetAmbABI, mainnetProvider) +} + +async function requiredSignaturesHaveBeenCollected(client, messageHash, options = {}) { + const sidechainAmb = await getSidechainAmb(client, options) + const requiredSignatureCount = await sidechainAmb.requiredSignatures() + + // Bit 255 is set to mark completion, double check though + const sigCountStruct = await sidechainAmb.numMessagesSigned(messageHash) + const collectedSignatureCount = sigCountStruct.mask(255) + const markedComplete = sigCountStruct.shr(255).gt(0) + + log(`${collectedSignatureCount.toString()} out of ${requiredSignatureCount.toString()} collected`) + if (markedComplete) { log('All signatures collected') } + return markedComplete +} + +// move signatures from sidechain to mainnet +async function transportSignatures(client, messageHash, options) { + const sidechainAmb = await getSidechainAmb(client, options) + const message = await sidechainAmb.message(messageHash) + const messageId = '0x' + message.substr(2, 64) + const sigCountStruct = await sidechainAmb.numMessagesSigned(messageHash) + const collectedSignatureCount = sigCountStruct.mask(255).toNumber() + + log(`${collectedSignatureCount} signatures reported, getting them from the sidechain AMB...`) + const signatures = await Promise.all(Array(collectedSignatureCount).fill(0).map(async (_, i) => sidechainAmb.signature(messageHash, i))) + + const [vArray, rArray, sArray] = [[], [], []] + signatures.forEach((signature, i) => { + log(` Signature ${i}: ${signature} (len=${signature.length}=${signature.length / 2 - 1} bytes)`) + rArray.push(signature.substr(2, 64)) + sArray.push(signature.substr(66, 64)) + vArray.push(signature.substr(130, 2)) }) - const json = await response.json() - // server may return things like { code: "ConnectionPoolTimeoutException", message: "Timeout waiting for connection from pool" } - // they must still be handled as errors - if (!response.ok && !json.error) { - json.error = `Server returned ${response.status} ${response.statusText}` - } + const packedSignatures = BigNumber.from(signatures.length).toHexString() + vArray.join('') + rArray.join('') + sArray.join('') + log(`All signatures packed into one: ${packedSignatures}`) + + // Gas estimation also checks that the transaction would succeed, and provides a helpful error message in case it would fail + const mainnetAmb = await getMainnetAmb(client, options) + log(`Estimating gas using mainnet AMB @ ${mainnetAmb.address}, message=${message}`) + let gasLimit + try { + // magic number suggested by https://github.com/poanetwork/tokenbridge/blob/master/oracle/src/utils/constants.js + gasLimit = await mainnetAmb.estimateGas.executeSignatures(message, packedSignatures) + 200000 + log(`Calculated gas limit: ${gasLimit.toString()}`) + } catch (e) { + // Failure modes from https://github.com/poanetwork/tokenbridge/blob/master/oracle/src/events/processAMBCollectedSignatures/estimateGas.js + log('Gas estimation failed: Check if the message was already processed') + const alreadyProcessed = await mainnetAmb.relayedMessages(messageId) + if (alreadyProcessed) { + log(`WARNING: Tried to transport signatures but they have already been transported (Message ${messageId} has already been processed)`) + log('This could happen if payForSignatureTransport=true, but bridge operator also pays for signatures, and got there before your client') + return null + } + + log('Gas estimation failed: Check if number of signatures is enough') + const mainnetProvider = client.getMainnetProvider() + const validatorContractAddress = await mainnetAmb.validatorContract() + const validatorContract = new Contract(validatorContractAddress, [{ + name: 'isValidator', + inputs: [{ type: 'address' }], + outputs: [{ type: 'bool' }], + stateMutability: 'view', + type: 'function' + }, { + name: 'requiredSignatures', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }], mainnetProvider) + const requiredSignatures = await validatorContract.requiredSignatures() + if (requiredSignatures.gt(signatures.length)) { + throw new Error('The number of required signatures does not match between sidechain(' + + signatures.length + ' and mainnet( ' + requiredSignatures.toString()) + } - if (json.code && !json.error) { - json.error = json.code + log('Gas estimation failed: Check if all the signatures were made by validators') + log(` Recover signer addresses from signatures [${signatures.join(', ')}]`) + const signers = signatures.map((signature) => verifyMessage(arrayify(message), signature)) + log(` Check that signers are validators [[${signers.join(', ')}]]`) + const isValidatorArray = await Promise.all(signers.map((address) => [address, validatorContract.isValidator(address)])) + const nonValidatorSigners = isValidatorArray.filter(([, isValidator]) => !isValidator) + if (nonValidatorSigners.length > 0) { + throw new Error(`Following signers are not listed as validators in mainnet validator contract at ${validatorContractAddress}:\n - ` + + nonValidatorSigners.map(([address]) => address).join('\n - ')) + } + + throw new Error(`Gas estimation failed: Unknown error while processing message ${message} with ${e.stack}`) } - return json + + const signer = client.getSigner() + log(`Sending message from signer=${await signer.getAddress()}`) + const txAMB = await mainnetAmb.connect(signer).executeSignatures(message, packedSignatures) + const trAMB = await txAMB.wait() + return trAMB } -async function getOrThrow(...args) { - const res = await get(...args) - if (res.error) { - throw new Error(JSON.stringify(res)) +// template for withdraw functions +// client could be replaced with AMB (mainnet and sidechain) +async function untilWithdrawIsComplete(client, getWithdrawTxFunc, getBalanceFunc, options = {}) { + const { + pollingIntervalMs = 1000, + retryTimeoutMs = 60000, + } = options + const balanceBefore = await getBalanceFunc(options) + const tx = await getWithdrawTxFunc(options) + const tr = await tx.wait() + + if (options.payForSignatureTransport) { + log(`Got receipt, filtering UserRequestForSignature from ${tr.events.length} events...`) + // event UserRequestForSignature(bytes32 indexed messageId, bytes encodedData); + const sigEventArgsArray = tr.events.filter((e) => e.event === 'UserRequestForSignature').map((e) => e.args) + if (sigEventArgsArray.length < 1) { + throw new Error("No UserRequestForSignature events emitted from withdraw transaction, can't transport withdraw to mainnet") + } + /* eslint-disable no-await-in-loop */ + // eslint-disable-next-line no-restricted-syntax + for (const eventArgs of sigEventArgsArray) { + const messageId = eventArgs[0] + const messageHash = keccak256(eventArgs[1]) + + log(`Waiting until sidechain AMB has collected required signatures for hash=${messageHash}...`) + await until(async () => requiredSignaturesHaveBeenCollected(client, messageHash, options), pollingIntervalMs, retryTimeoutMs) + + log(`Checking mainnet AMB hasn't already processed messageId=${messageId}`) + const mainnetAmb = await getMainnetAmb(client, options) + const alreadySent = await mainnetAmb.messageCallStatus(messageId) + const failAddress = await mainnetAmb.failedMessageSender(messageId) + if (alreadySent || failAddress !== '0x0000000000000000000000000000000000000000') { // zero address means no failed messages + log(`WARNING: Mainnet bridge has already processed withdraw messageId=${messageId}`) + log('This could happen if payForSignatureTransport=true, but bridge operator also pays for signatures, and got there before your client') + continue + } + + log(`Transporting signatures for hash=${messageHash}`) + await transportSignatures(client, messageHash, options) + } + /* eslint-enable no-await-in-loop */ } - return res + + log(`Waiting for balance ${balanceBefore.toString()} to change`) + await until(async () => !(await getBalanceFunc(options)).eq(balanceBefore), retryTimeoutMs, pollingIntervalMs) + + return tr } -/** - * @typedef {Object} EthereumOptions all optional, hence "options" - * @property {Wallet | String} wallet or private key, default is currently logged in StreamrClient (if auth: privateKey) - * @property {String} key private key, alias for String wallet - * @property {String} privateKey, alias for String wallet - * @property {providers.Provider} provider to use in case wallet was a String, or omitted - * @property {Number} confirmations, default is 1 - * @property {BigNumber} gasPrice in wei (part of ethers overrides), default is whatever the network recommends (ethers.js default) - * @see https://docs.ethers.io/ethers.js/html/api-contract.html#overrides - */ +// TODO: calculate addresses in JS instead of asking over RPC, see data-union-solidity/contracts/CloneLib.sol +// key the cache with name only, since PROBABLY one StreamrClient will ever use only one private key +const mainnetAddressCache = {} // mapping: "name" -> mainnet address +/** @returns {Promise} Mainnet address for Data Union */ +async function getDataUnionMainnetAddress(client, dataUnionName, deployerAddress, options = {}) { + if (!mainnetAddressCache[dataUnionName]) { + const provider = client.getMainnetProvider() + const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress + const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, provider) + const addressPromise = factoryMainnet.mainnetAddress(deployerAddress, dataUnionName) + mainnetAddressCache[dataUnionName] = addressPromise + mainnetAddressCache[dataUnionName] = await addressPromise // eslint-disable-line require-atomic-updates + } + return mainnetAddressCache[dataUnionName] +} -// TODO: gasPrice to overrides (not needed for browser, but would be useful in node.js) +// TODO: calculate addresses in JS +const sidechainAddressCache = {} // mapping: mainnet address -> sidechain address +/** @returns {Promise} Sidechain address for Data Union */ +async function getDataUnionSidechainAddress(client, duMainnetAddress, options = {}) { + if (!sidechainAddressCache[duMainnetAddress]) { + const provider = client.getMainnetProvider() + const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress + const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, provider) + const addressPromise = factoryMainnet.sidechainAddress(duMainnetAddress) + sidechainAddressCache[duMainnetAddress] = addressPromise + sidechainAddressCache[duMainnetAddress] = await addressPromise // eslint-disable-line require-atomic-updates + } + return sidechainAddressCache[duMainnetAddress] +} -/** - * Get a wallet from options, e.g. by parsing something that looks like a private key - * @param {StreamrClient} client this - * @param {EthereumOptions} options includes wallet which is Wallet or private key, or provider so StreamrClient auth: privateKey will be used - * @returns {Wallet} "wallet with provider" that can be used to sign and send transactions - */ -function parseWalletFromOptions(client, options) { - if (options.wallet instanceof Wallet) { return options.wallet } +function getMainnetContractReadOnly(client, options = {}) { + let dataUnion = options.dataUnion || options.dataUnionAddress || client.options.dataUnion + if (isAddress(dataUnion)) { + const provider = client.getMainnetProvider() + dataUnion = new Contract(dataUnion, dataUnionMainnetABI, provider) + } - const key = typeof options.wallet === 'string' ? options.wallet : options.key || options.privateKey || client.options.auth.privateKey - if (key) { - const provider = options.provider instanceof providers.Provider ? options.provider : getDefaultProvider() - return new Wallet(key, provider) + if (!(dataUnion instanceof Contract)) { + throw new Error(`Option dataUnion=${dataUnion} was not a good Ethereum address or Contract`) } + return dataUnion +} + +function getMainnetContract(client, options = {}) { + const du = getMainnetContractReadOnly(client, options) + const signer = client.getSigner() + return du.connect(signer) +} - // TODO: check metamask before erroring! - throw new Error("Please provide options.wallet, or options.privateKey string, if you're not authenticated using a privateKey") +async function getSidechainContract(client, options = {}) { + const signer = await client.getSidechainSigner() + const duMainnet = getMainnetContractReadOnly(client, options) + const duSidechainAddress = await getDataUnionSidechainAddress(client, duMainnet.address, options) + const duSidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, signer) + return duSidechain +} + +async function getSidechainContractReadOnly(client, options = {}) { + const provider = await client.getSidechainProvider() + const duMainnet = getMainnetContractReadOnly(client, options) + const duSidechainAddress = await getDataUnionSidechainAddress(client, duMainnet.address, options) + const duSidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, provider) + return duSidechain } // ////////////////////////////////////////////////////////////////// // admin: DEPLOY AND SETUP DATA UNION // ////////////////////////////////////////////////////////////////// +export async function calculateDataUnionMainnetAddress(dataUnionName, deployerAddress, options) { + const address = getAddress(deployerAddress) // throws if bad address + return getDataUnionMainnetAddress(this, dataUnionName, address, options) +} + +export async function calculateDataUnionSidechainAddress(duMainnetAddress, options) { + const address = getAddress(duMainnetAddress) // throws if bad address + return getDataUnionSidechainAddress(this, address, options) +} + +/** + * TODO: update this comment + * @typedef {object} EthereumOptions all optional, hence "options" + * @property {Wallet | string} wallet or private key, default is currently logged in StreamrClient (if auth: privateKey) + * @property {string} key private key, alias for String wallet + * @property {string} privateKey, alias for String wallet + * @property {providers.Provider} provider to use in case wallet was a String, or omitted + * @property {number} confirmations, default is 1 + * @property {BigNumber} gasPrice in wei (part of ethers overrides), default is whatever the network recommends (ethers.js default) + * @see https://docs.ethers.io/ethers.js/html/api-contract.html#overrides + */ /** - * Deploy a new DataUnion contract and create the required joinPartStream - * Note that the Promise resolves with an ethers.js TransactionResponse, so it's only sent to the chain at that point, but not yet deployed - * @param {EthereumOptions} options such as blockFreezePeriodSeconds (default: 0), adminFee (default: 0) - * @return {Promise} has methods that can be awaited: contract is deployed (`.deployed()`), operator is started (`.isReady()`) + * @typedef {object} AdditionalDeployOptions for deployDataUnion + * @property {EthereumAddress} owner new data union owner, defaults to StreamrClient authenticated user + * @property {Array} joinPartAgents defaults to just the owner + * @property {number} adminFee fraction (number between 0...1 where 1 means 100%) + * @property {EthereumAddress} factoryMainnetAddress defaults to StreamrClient options + * @property {string} dataUnionName unique (to the DataUnionFactory) identifier of the new data union, must not exist yet */ -export async function deployDataUnion(options) { - const wallet = parseWalletFromOptions(this, options) +/** + * @typedef {EthereumOptions & AdditionalDeployOptions} DeployOptions + */ +// TODO: gasPrice to overrides (not needed for browser, but would be useful in node.js) + +/** + * Create a new DataUnionMainnet contract to mainnet with DataUnionFactoryMainnet + * This triggers DataUnionSidechain contract creation in sidechain, over the bridge (AMB) + * @param {DeployOptions} options such as adminFee (default: 0) + * @return {Promise} that resolves when the new DU is deployed over the bridge to side-chain + */ +export async function deployDataUnion(options = {}) { const { - blockFreezePeriodSeconds = 0, + owner, + joinPartAgents, + dataUnionName, adminFee = 0, - tokenAddress = this.options.tokenAddress, - streamrNodeAddress = this.options.streamrNodeAddress, - streamrOperatorAddress = this.options.streamrOperatorAddress + sidechainPollingIntervalMs = 1000, + sidechainRetryTimeoutMs = 600000, } = options - await throwIfNotContract(wallet.provider, tokenAddress, 'options.tokenAddress') - await throwIfBadAddress(streamrNodeAddress, 'options.streamrNodeAddress') - await throwIfBadAddress(streamrOperatorAddress, 'options.streamrOperatorAddress') + let duName = dataUnionName + if (!duName) { + duName = `DataUnion-${Date.now()}` // TODO: use uuid + log(`dataUnionName generated: ${duName}`) + } if (adminFee < 0 || adminFee > 1) { throw new Error('options.adminFeeFraction must be a number between 0...1, got: ' + adminFee) } const adminFeeBN = BigNumber.from((adminFee * 1e18).toFixed()) // last 2...3 decimals are going to be gibberish - const stream = await this.createStream({ - name: `Join-Part-${wallet.address.slice(0, 10)}-${Date.now()}` - }) - debug(`Stream created: ${JSON.stringify(stream.toObject())}`) + const mainnetProvider = this.getMainnetProvider() + const mainnetWallet = this.getSigner() + const sidechainProvider = this.getSidechainProvider() - let res - res = await stream.grantPermission('stream_get', null) - debug(`Grant stream_get permission response from server: ${JSON.stringify(res)}`) - res = await stream.grantPermission('stream_subscribe', null) - debug(`Grant stream_subscribe permission response from server: ${JSON.stringify(res)}`) - res = await stream.grantPermission('stream_get', streamrNodeAddress) - debug(`Grant stream_get permission response to ${streamrNodeAddress} from server: ${JSON.stringify(res)}`) - res = await stream.grantPermission('stream_publish', streamrNodeAddress) - debug(`Grant stream_publish permission response to ${streamrNodeAddress} from server: ${JSON.stringify(res)}`) + // parseAddress defaults to authenticated user (also if "owner" is not an address) + const ownerAddress = await parseAddress(this, owner) - const deployer = new ContractFactory(DataUnion.abi, DataUnion.bytecode, wallet) - const result = await deployer.deploy(streamrOperatorAddress, stream.id, tokenAddress, blockFreezePeriodSeconds, adminFeeBN) - const { address } = result // this can be known in advance - debug(`Data Union contract @ ${address} deployment started`) + let agentAddressList + if (Array.isArray(joinPartAgents)) { + // getAddress throws if there's an invalid address in the array + agentAddressList = joinPartAgents.map(getAddress) + } else { + // streamrNode needs to be joinPartAgent so that EE join with secret works (and join approvals from Marketplace UI) + agentAddressList = [ownerAddress] + if (this.options.streamrNodeAddress) { + agentAddressList.push(getAddress(this.options.streamrNodeAddress)) + } + } - // add the waiting method so that caller can await data union being operated by server (so that EE calls work) - const client = this - result.isReady = async (pollingIntervalMs, timeoutMs) => client.dataUnionIsReady(address, pollingIntervalMs, timeoutMs) - return result -} + const duMainnetAddress = await getDataUnionMainnetAddress(this, duName, ownerAddress, options) + const duSidechainAddress = await getDataUnionSidechainAddress(this, duMainnetAddress, options) -/** - * Await this function when you want to make sure a data union is deployed and ready to use - * @param {EthereumAddress} dataUnionContractAddress - * @param {Number} pollingIntervalMs (optional, default: 1000) ask server if data union is ready - * @param {Number} retryTimeoutMs (optional, default: 60000) give up sending more retries - * @return {Promise} resolves when data union server is ready to operate the data union (or fails with HTTP error) - */ -export async function dataUnionIsReady(dataUnionContractAddress, pollingIntervalMs = 1000, retryTimeoutMs = 60000) { - let stats = await get(this, dataUnionContractAddress, '/stats') - const startTime = Date.now() - while (stats.error && Date.now() < startTime + retryTimeoutMs && (!stats.dataUnion || stats.dataUnion.state !== 'failed')) { - debug(`Waiting for data union ${dataUnionContractAddress} to start. Status: ${JSON.stringify(stats)}`) - await sleep(pollingIntervalMs) // eslint-disable-line no-await-in-loop - stats = await get(this, dataUnionContractAddress, '/stats') // eslint-disable-line no-await-in-loop + if (await mainnetProvider.getCode(duMainnetAddress) !== '0x') { + throw new Error(`Mainnet data union "${duName}" contract ${duMainnetAddress} already exists!`) } - if (stats.error) { - throw new Error(`Data Union failed to start, retried for ${retryTimeoutMs} ms. Status: ${JSON.stringify(stats)}`) + + const factoryMainnetAddress = throwIfBadAddress( + options.factoryMainnetAddress || this.options.factoryMainnetAddress, + 'StreamrClient.options.factoryMainnetAddress' + ) + if (await mainnetProvider.getCode(factoryMainnetAddress) === '0x') { + throw new Error(`Data union factory contract not found at ${factoryMainnetAddress}, check StreamrClient.options.factoryMainnetAddress!`) } + + // function deployNewDataUnion(address owner, uint256 adminFeeFraction, address[] agents, string duName) + const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, mainnetWallet) + const tx = await factoryMainnet.deployNewDataUnion( + ownerAddress, + adminFeeBN, + agentAddressList, + duName, + ) + const tr = await tx.wait() + + log(`Data Union "${duName}" (mainnet: ${duMainnetAddress}, sidechain: ${duSidechainAddress}) deployed to mainnet, waiting for side-chain...`) + await until( + async () => await sidechainProvider.getCode(duSidechainAddress) !== '0x', + sidechainRetryTimeoutMs, + sidechainPollingIntervalMs + ) + + const dataUnion = new Contract(duMainnetAddress, dataUnionMainnetABI, mainnetWallet) + dataUnion.deployTxReceipt = tr + dataUnion.sidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, sidechainProvider) + return dataUnion +} + +export async function getDataUnionContract(options = {}) { + const ret = getMainnetContract(this, options) + ret.sidechain = await getSidechainContract(this, options) + return ret } /** * Add a new data union secret - * @param {EthereumAddress} dataUnionContractAddress + * @param {EthereumAddress} dataUnionMainnetAddress * @param {String} name describes the secret * @returns {String} the server-generated secret */ -export async function createSecret(dataUnionContractAddress, name = 'Untitled Data Union Secret') { - const duAddress = getAddress(dataUnionContractAddress) // throws if bad address +export async function createSecret(dataUnionMainnetAddress, name = 'Untitled Data Union Secret') { + const duAddress = getAddress(dataUnionMainnetAddress) // throws if bad address const url = getEndpointUrl(this.options.restUrl, 'dataunions', duAddress, 'secrets') const res = await authFetch( url, @@ -213,27 +676,158 @@ export async function createSecret(dataUnionContractAddress, name = 'Untitled Da return res.secret } +// ////////////////////////////////////////////////////////////////// +// admin: MANAGE DATA UNION +// ////////////////////////////////////////////////////////////////// + +/** + * Kick given members from data union + * @param {List} memberAddressList to kick + * @returns {Promise} partMembers sidechain transaction + */ +export async function kick(memberAddressList, options = {}) { + const members = memberAddressList.map(getAddress) // throws if there are bad addresses + const duSidechain = await getSidechainContract(this, options) + const tx = await duSidechain.partMembers(members) + // TODO: wrap promise for better error reporting in case tx fails (parse reason, throw proper error) + return tx.wait(options.confirmations || 1) +} + +/** + * Add given Ethereum addresses as data union members + * @param {List} memberAddressList to add + * @returns {Promise} addMembers sidechain transaction + */ +export async function addMembers(memberAddressList, options = {}) { + const members = memberAddressList.map(getAddress) // throws if there are bad addresses + const duSidechain = await getSidechainContract(this, options) + const tx = await duSidechain.addMembers(members) + // TODO: wrap promise for better error reporting in case tx fails (parse reason, throw proper error) + return tx.wait(options.confirmations || 1) +} + +/** + * Admin: withdraw earnings (pay gas) on behalf of a member + * TODO: add test + * @param {EthereumAddress} memberAddress the other member who gets their tokens out of the Data Union + * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) + * @returns {Promise} get receipt once withdraw transaction is confirmed + */ +export async function withdrawMember(memberAddress, options) { + const address = getAddress(memberAddress) // throws if bad address + const tr = await untilWithdrawIsComplete( + this, + this.getWithdrawMemberTx.bind(this, address), + this.getTokenBalance.bind(this, address), + { ...this.options, ...options } + ) + return tr +} + +/** + * Admin: get the tx promise for withdrawing all earnings on behalf of a member + * @param {EthereumAddress} memberAddress the other member who gets their tokens out of the Data Union + * @param {EthereumAddress} dataUnion to withdraw my earnings from + * @param {EthereumOptions} options + * @returns {Promise} await on call .wait to actually send the tx + */ +export async function getWithdrawMemberTx(memberAddress, options) { + const a = getAddress(memberAddress) // throws if bad address + const duSidechain = await getSidechainContract(this, options) + return duSidechain.withdrawAll(a, true) // sendToMainnet=true +} + +/** + * Admin: Withdraw a member's earnings to another address, signed by the member + * @param {EthereumAddress} dataUnion to withdraw my earnings from + * @param {EthereumAddress} memberAddress the member whose earnings are sent out + * @param {EthereumAddress} recipientAddress the address to receive the tokens in mainnet + * @param {string} signature from member, produced using signWithdrawTo + * @param {EthereumOptions} options + * @returns {Promise} get receipt once withdraw transaction is confirmed + */ +export async function withdrawToSigned(memberAddress, recipientAddress, signature, options) { + const from = getAddress(memberAddress) // throws if bad address + const to = getAddress(recipientAddress) + const tr = await untilWithdrawIsComplete( + this, + this.getWithdrawToSignedTx.bind(this, from, to, signature), + this.getTokenBalance.bind(this, to), + { ...this.options, ...options } + ) + return tr +} + +/** + * Admin: Withdraw a member's earnings to another address, signed by the member + * @param {EthereumAddress} dataUnion to withdraw my earnings from + * @param {EthereumAddress} memberAddress the member whose earnings are sent out + * @param {EthereumAddress} recipientAddress the address to receive the tokens in mainnet + * @param {string} signature from member, produced using signWithdrawTo + * @param {EthereumOptions} options + * @returns {Promise} await on call .wait to actually send the tx + */ +export async function getWithdrawToSignedTx(memberAddress, recipientAddress, signature, options) { + const duSidechain = await getSidechainContract(this, options) + return duSidechain.withdrawAllToSigned(memberAddress, recipientAddress, true, signature) // sendToMainnet=true +} + +/** + * Admin: set admin fee for the data union + * @param {number} newFeeFraction between 0.0 and 1.0 + * @param {EthereumOptions} options + */ +export async function setAdminFee(newFeeFraction, options) { + if (newFeeFraction < 0 || newFeeFraction > 1) { + throw new Error('newFeeFraction argument must be a number between 0...1, got: ' + newFeeFraction) + } + const adminFeeBN = BigNumber.from((newFeeFraction * 1e18).toFixed()) // last 2...3 decimals are going to be gibberish + const duMainnet = getMainnetContract(this, options) + const tx = await duMainnet.setAdminFee(adminFeeBN) + return tx.wait() +} + +/** + * Get data union admin fee fraction that admin gets from each revenue event + * @returns {number} between 0.0 and 1.0 + */ +export async function getAdminFee(options) { + const duMainnet = getMainnetContractReadOnly(this, options) + const adminFeeBN = await duMainnet.adminFeeFraction() + return +adminFeeBN.toString() / 1e18 +} + +export async function getAdminAddress(options) { + const duMainnet = getMainnetContractReadOnly(this, options) + return duMainnet.owner() +} + // ////////////////////////////////////////////////////////////////// // member: JOIN & QUERY DATA UNION // ////////////////////////////////////////////////////////////////// /** * Send a joinRequest, or get into data union instantly with a data union secret - * @param {EthereumAddress} dataUnionContractAddress to join - * @param {String} secret (optional) if given, and correct, join the data union immediately + * @param {JoinOptions} options + * + * @typedef {object} JoinOptions + * @property {String} dataUnion Ethereum mainnet address of the data union. If not given, use one given when creating StreamrClient + * @property {String} member Ethereum mainnet address of the joining member. If not given, use StreamrClient authentication key + * @property {String} secret if given, and correct, join the data union immediately */ -export async function joinDataUnion(dataUnionContractAddress, secret) { - const authKey = this.options.auth && this.options.auth.privateKey - if (!authKey) { - throw new Error('joinDataUnion: StreamrClient must have auth: privateKey') - } +export async function joinDataUnion(options = {}) { + const { + member, + secret, + } = options + const dataUnion = getMainnetContractReadOnly(this, options) const body = { - memberAddress: computeAddress(authKey) + memberAddress: parseAddress(this, member, options) } if (secret) { body.secret = secret } - const url = getEndpointUrl(this.options.restUrl, 'communities', dataUnionContractAddress, 'joinRequests') + const url = getEndpointUrl(this.options.restUrl, 'dataunions', dataUnion.address, 'joinRequests') return authFetch( url, this.session, @@ -249,270 +843,239 @@ export async function joinDataUnion(dataUnionContractAddress, secret) { /** * Await this function when you want to make sure a member is accepted in the data union - * @param {EthereumAddress} dataUnionContractAddress * @param {EthereumAddress} memberAddress (optional, default is StreamrClient's auth: privateKey) * @param {Number} pollingIntervalMs (optional, default: 1000) ask server if member is in * @param {Number} retryTimeoutMs (optional, default: 60000) give up * @return {Promise} resolves when member is in the data union (or fails with HTTP error) */ -export async function hasJoined(dataUnionContractAddress, memberAddress, pollingIntervalMs = 1000, retryTimeoutMs = 60000) { - let address = memberAddress - if (!address) { - const authKey = this.options.auth && this.options.auth.privateKey - if (!authKey) { - throw new Error("StreamrClient wasn't authenticated with privateKey, and memberAddress argument not supplied") - } - address = computeAddress(authKey) - } +export async function hasJoined(memberAddress, options = {}) { + const { + pollingIntervalMs = 1000, + retryTimeoutMs = 60000, + } = options + const address = parseAddress(this, memberAddress, options) + const duSidechain = await getSidechainContractReadOnly(this, options) - let stats = await get(this, dataUnionContractAddress, `/members/${address}`) - const startTime = Date.now() - while (stats.error && Date.now() < startTime + retryTimeoutMs && (!stats.dataUnion || stats.dataUnion.state !== 'failed')) { - debug(`Waiting for member ${address} to be accepted into data union ${dataUnionContractAddress}. Status: ${JSON.stringify(stats)}`) - await sleep(pollingIntervalMs) // eslint-disable-line no-await-in-loop - stats = await get(this, dataUnionContractAddress, `/members/${address}`) // eslint-disable-line no-await-in-loop - } - if (stats.error) { - throw new Error(`Member failed to join, retried for ${retryTimeoutMs} ms. Status: ${JSON.stringify(stats)}`) + // memberData[0] is enum ActiveStatus {None, Active, Inactive}, and zero means member has never joined + await until(async () => (await duSidechain.memberData(address))[0] !== 0, retryTimeoutMs, pollingIntervalMs) +} + +// TODO: this needs more thought: probably something like getEvents from sidechain? Heavy on RPC? +export async function getMembers(options) { + const duSidechain = await getSidechainContractReadOnly(this, options) + throw new Error(`Not implemented for side-chain data union (at ${duSidechain.address})`) + // event MemberJoined(address indexed); + // event MemberParted(address indexed); +} + +export async function getDataUnionStats(options) { + const duSidechain = await getSidechainContractReadOnly(this, options) + const [ + totalEarnings, + totalEarningsWithdrawn, + activeMemberCount, + inactiveMemberCount, + lifetimeMemberEarnings, + joinPartAgentCount, + ] = await duSidechain.getStats() + const totalWithdrawable = totalEarnings.sub(totalEarningsWithdrawn) + return { + activeMemberCount, + inactiveMemberCount, + joinPartAgentCount, + totalEarnings, + totalWithdrawable, + lifetimeMemberEarnings, } } /** - * Get stats of a single data union member, including proof - * @param {EthereumAddress} dataUnionContractAddress to query + * Get stats of a single data union member + * @param {EthereumAddress} dataUnion to query * @param {EthereumAddress} memberAddress (optional) if not supplied, get the stats of currently logged in StreamrClient (if auth: privateKey) */ -export async function getMemberStats(dataUnionContractAddress, memberAddress) { - let address = memberAddress - if (!address) { - const authKey = this.options.auth && this.options.auth.privateKey - if (!authKey) { - throw new Error("StreamrClient wasn't authenticated with privateKey, and memberAddress argument not supplied") - } - address = computeAddress(authKey) +export async function getMemberStats(memberAddress, options) { + const address = parseAddress(this, memberAddress, options) + // TODO: use duSidechain.getMemberStats(address) once it's implemented, to ensure atomic read + // (so that memberData is from same block as getEarnings, otherwise withdrawable will be foobar) + const duSidechain = await getSidechainContractReadOnly(this, options) + const mdata = await duSidechain.memberData(address) + const total = await duSidechain.getEarnings(address).catch(() => 0) + const withdrawnEarnings = mdata[3].toString() + const withdrawable = total ? total.sub(withdrawnEarnings) : 0 + return { + status: ['unknown', 'active', 'inactive', 'blocked'][mdata[0]], + earningsBeforeLastJoin: mdata[1].toString(), + lmeAtJoin: mdata[2].toString(), + totalEarnings: total.toString(), + withdrawableEarnings: withdrawable.toString(), } - - return getOrThrow(this, dataUnionContractAddress, `/members/${address}`) } /** - * @typedef {Object} BalanceResponse - * @property {BigNumber} total tokens earned less withdrawn previously, what you'd get once Operator commits the earnings to DataUnion contract - * @property {BigNumber} withdrawable number of tokens that you'd get if you withdraw now - */ - -/** - * Calculate the amount of tokens the member would get from a successful withdraw - * @param dataUnionContractAddress - * @param memberAddress - * @return {Promise} earnings minus withdrawn tokens + * Get the amount of tokens the member would get from a successful withdraw + * @param dataUnion to query + * @param memberAddress whose balance is returned + * @return {Promise} */ -export async function getBalance(dataUnionContractAddress, memberAddress, provider) { - let address = memberAddress - if (!address) { - const authKey = this.options.auth && this.options.auth.privateKey - if (!authKey) { - throw new Error("StreamrClient wasn't authenticated with privateKey, and memberAddress argument not supplied") - } - address = computeAddress(authKey) - } - - const stats = await get(this, dataUnionContractAddress, `/members/${address}`) - if (stats.error || stats.earnings === '0') { - return { - total: BigNumber.ZERO, withdrawable: BigNumber.ZERO - } - } - const earningsBN = BigNumber.from(stats.earnings) - - if (stats.withdrawableEarnings === '0') { - return { - total: earningsBN, withdrawable: BigNumber.ZERO - } - } - const withdrawableEarningsBN = BigNumber.from(stats.withdrawableEarnings) - - const dataUnionContract = new Contract(dataUnionContractAddress, DataUnion.abi, provider || getDefaultProvider()) - const withdrawnBN = await dataUnionContract.withdrawn(address) - const total = earningsBN.sub(withdrawnBN) - const withdrawable = withdrawableEarningsBN.sub(withdrawnBN) - return { - total, withdrawable - } +export async function getMemberBalance(memberAddress, options) { + const address = parseAddress(this, memberAddress, options) + const duSidechain = await getSidechainContractReadOnly(this, options) + return duSidechain.getWithdrawableEarnings(address) } -// TODO: filter? That JSON blob could be big -export async function getMembers(dataUnionContractAddress) { - return getOrThrow(this, dataUnionContractAddress, '/members') +export async function getTokenBalance(address, options) { + const a = parseAddress(this, address, options) + const tokenAddressMainnet = this.options.tokenAddress || options.tokenAddress + if (!tokenAddressMainnet) { throw new Error('tokenAddress option not found') } + const provider = this.getMainnetProvider() + const token = new Contract(tokenAddressMainnet, [{ + name: 'balanceOf', + inputs: [{ type: 'address' }], + outputs: [{ type: 'uint256' }], + constant: true, + payable: false, + stateMutability: 'view', + type: 'function' + }], provider) + return token.balanceOf(a) } -export async function getDataUnionStats(dataUnionContractAddress) { - return getOrThrow(this, dataUnionContractAddress, '/stats') +/** + * Figure out if given mainnet address is old DataUnion (v 1.0) or current 2.0 + * NOTE: Current version of streamr-client-javascript can only handle current version! + * @param {EthereumAddress} contractAddress + * @returns {number} 1 for old, 2 for current, zero for "not a data union" + */ +export async function getDataUnionVersion(contractAddress) { + const a = getAddress(contractAddress) // throws if bad address + const provider = this.getMainnetProvider() + const du = new Contract(a, [{ + name: 'version', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }], provider) + try { + const version = await du.version() + return +version + } catch (e) { + return 0 + } } // ////////////////////////////////////////////////////////////////// // member: WITHDRAW EARNINGS // ////////////////////////////////////////////////////////////////// -/* eslint-disable no-await-in-loop, no-else-return */ -/** - * Validate the proof given by the server with the smart contract (ground truth) - * Wait for options.retryBlocks Ethereum blocks (default: 5) - * @param {EthereumAddress} dataUnionContractAddress to query - * @param {EthereumAddress} memberAddress to query - * @param {providers.Provider} provider (optional) e.g. `wallet.provider`, default is `ethers.getDefaultProvider()` (mainnet) - * @return {Object} containing the validated proof, withdrawableEarnings and withdrawableBlock - */ -export async function validateProof(dataUnionContractAddress, options) { - const wallet = parseWalletFromOptions(this, options) - const contract = new Contract(dataUnionContractAddress, DataUnion.abi, wallet) - - const { retryBlocks = 5 } = options - for (let retryCount = 0; retryCount < retryBlocks; retryCount++) { - const stats = await this.getMemberStats(dataUnionContractAddress, wallet.address) // throws on connection errors - if (!stats.withdrawableBlockNumber) { - throw new Error('No earnings to withdraw.') - } - const wasCorrect = await contract.proofIsCorrect( - stats.withdrawableBlockNumber, - wallet.address, - stats.withdrawableEarnings, - stats.proof, - ).catch((e) => e) - if (wasCorrect === true) { - return stats - } else if (wasCorrect === false) { - console.error(`Server gave bad proof: ${JSON.stringify(stats)}`) - } else if (wasCorrect instanceof Error && wasCorrect.message.endsWith('error_blockNotFound')) { - // commit hasn't been yet accepted into blockchain, just wait until next block and try again - } else { - console.error(`Unexpected: ${wasCorrect}`) - } - await new Promise((resolve) => { - wallet.provider.once('block', resolve) - }) - } - throw new Error(`Failed to validate proof after ${retryBlocks} Ethereum blocks`) -} -/* eslint-enable no-await-in-loop, no-else-return */ - /** * Withdraw all your earnings - * @param {EthereumAddress} dataUnionContractAddress - * @param {EthereumOptions} options - * @returns {Promise} get receipt once withdraw transaction is confirmed + * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) + * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) */ -export async function withdraw(dataUnionContractAddress, options) { - const tx = await this.getWithdrawTx(dataUnionContractAddress, options) - return tx.wait(options.confirmations || 1) +export async function withdraw(options = {}) { + const tr = await untilWithdrawIsComplete( + this, + this.getWithdrawTx.bind(this), + this.getTokenBalance.bind(this, null), // null means this StreamrClient's auth credentials + { ...this.options, ...options } + ) + return tr } /** * Get the tx promise for withdrawing all your earnings - * @param {EthereumAddress} dataUnionContractAddress - * @param {EthereumOptions} options + * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} await on call .wait to actually send the tx */ -export async function getWithdrawTx(dataUnionContractAddress, options) { - const wallet = parseWalletFromOptions(this, options) - const stats = await this.getMemberStats(dataUnionContractAddress, wallet.address) // throws on connection errors - if (!stats.withdrawableBlockNumber) { - throw new Error(`No earnings to withdraw. Server response: ${JSON.stringify(stats)}`) - } - const contract = new Contract(dataUnionContractAddress, DataUnion.abi, wallet) - return contract.withdrawAll(stats.withdrawableBlockNumber, stats.withdrawableEarnings, stats.proof) -} +export async function getWithdrawTx(options) { + const signer = await this.getSidechainSigner() + const address = await signer.getAddress() + const duSidechain = await getSidechainContract(this, options) -/** - * Withdraw earnings (pay gas) on behalf of another member - * @param {EthereumAddress} memberAddress the other member who gets their tokens out of the Data Union - * @param {EthereumAddress} dataUnionContractAddress - * @param {EthereumOptions} options - * @returns {Promise} get receipt once withdraw transaction is confirmed - */ -export async function withdrawFor(memberAddress, dataUnionContractAddress, options) { - const tx = await this.getWithdrawTxFor(memberAddress, dataUnionContractAddress, options) - return tx.wait(options.confirmations || 1) -} + const withdrawable = await duSidechain.getWithdrawableEarnings(address) + if (withdrawable.eq(0)) { + throw new Error(`${address} has nothing to withdraw in (sidechain) data union ${duSidechain.address}`) + } -/** - * Get the tx promise for withdrawing all earnings on behalf of another member - * @param {EthereumAddress} dataUnionContractAddress - * @param {EthereumOptions} options - * @returns {Promise} await on call .wait to actually send the tx - */ -export async function getWithdrawTxFor(memberAddress, dataUnionContractAddress, options) { - const stats = await this.getMemberStats(dataUnionContractAddress, memberAddress) // throws on connection errors - if (!stats.withdrawableBlockNumber) { - throw new Error(`No earnings to withdraw. Server response: ${JSON.stringify(stats)}`) + if (this.options.minimumWithdrawTokenWei && withdrawable.lt(this.options.minimumWithdrawTokenWei)) { + throw new Error(`${address} has only ${withdrawable} to withdraw in ` + + `(sidechain) data union ${duSidechain.address} (min: ${this.options.minimumWithdrawTokenWei})`) } - const wallet = parseWalletFromOptions(this, options) - const contract = new Contract(dataUnionContractAddress, DataUnion.abi, wallet) - return contract.withdrawAllFor(memberAddress, stats.withdrawableBlockNumber, stats.withdrawableEarnings, stats.proof) + return duSidechain.withdrawAll(address, true) // sendToMainnet=true } /** * Withdraw earnings and "donate" them to the given address - * @param {EthereumAddress} dataUnionContractAddress * @param {EthereumAddress} recipientAddress the address to receive the tokens - * @param {EthereumOptions} options - * @returns {Promise} get receipt once withdraw transaction is confirmed + * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) + * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) */ -export async function withdrawTo(recipientAddress, dataUnionContractAddress, options) { - const tx = await this.getWithdrawTxTo(recipientAddress, dataUnionContractAddress, options) - return tx.wait(options.confirmations || 1) +export async function withdrawTo(recipientAddress, options = {}) { + const to = getAddress(recipientAddress) // throws if bad address + const tr = await untilWithdrawIsComplete( + this, + this.getWithdrawTxTo.bind(this, to), + this.getTokenBalance.bind(this, to), + { ...this.options, ...options } + ) + return tr } /** * Withdraw earnings and "donate" them to the given address - * @param {EthereumAddress} dataUnionContractAddress * @param {EthereumAddress} recipientAddress the address to receive the tokens - * @param {EthereumOptions} options + * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} await on call .wait to actually send the tx */ -export async function getWithdrawTxTo(recipientAddress, dataUnionContractAddress, options) { - const wallet = parseWalletFromOptions(this, options) - const stats = await this.getMemberStats(dataUnionContractAddress, wallet.address) // throws on connection errors - if (!stats.withdrawableBlockNumber) { - throw new Error(`No earnings to withdraw. Server response: ${JSON.stringify(stats)}`) +export async function getWithdrawTxTo(recipientAddress, options) { + const signer = await this.getSidechainSigner() + const address = await signer.getAddress() + const duSidechain = await getSidechainContract(this, options) + const withdrawable = await duSidechain.getWithdrawableEarnings(address) + if (withdrawable.eq(0)) { + throw new Error(`${address} has nothing to withdraw in (sidechain) data union ${duSidechain.address}`) } - const contract = new Contract(dataUnionContractAddress, DataUnion.abi, wallet) - return contract.withdrawAllTo(recipientAddress, stats.withdrawableBlockNumber, stats.withdrawableEarnings, stats.proof, options) -} - -// ////////////////////////////////////////////////////////////////// -// admin: MANAGE DATA UNION -// ////////////////////////////////////////////////////////////////// - -/** - * Directly poke into joinPartStream, circumventing EE joinRequest tools etc. - * Obviously requires write access to the stream, so only available to admins - * TODO: find a way to check that the join/part has gone through and been registered by the server - */ -async function sendToJoinPartStream(client, type, dataUnionContractAddress, addresses, provider) { - const contract = new Contract(dataUnionContractAddress, DataUnion.abi, provider || getDefaultProvider()) - const joinPartStreamId = await contract.joinPartStream() - return client.publish(joinPartStreamId, { - type, addresses, - }) + return duSidechain.withdrawAllTo(recipientAddress, true) // sendToMainnet=true } /** - * Kick given members from data union - * @param {EthereumAddress} dataUnionContractAddress to manage - * @param {List} memberAddressList to kick - * @param {providers.Provider} provider (optional) default is mainnet + * Member can sign off to "donate" all earnings to another address such that someone else + * can submit the transaction (and pay for the gas) + * This signature is only valid until next withdrawal takes place (using this signature or otherwise). + * Note that while it's a "blank cheque" for withdrawing all earnings at the moment it's used, it's + * invalidated by the first withdraw after signing it. In other words, any signature can be invalidated + * by making a "normal" withdraw e.g. `await streamrClient.withdraw()` + * Admin can execute the withdraw using this signature: ``` + * await adminStreamrClient.withdrawToSigned(memberAddress, recipientAddress, signature) + * ``` + * @param {EthereumAddress} recipientAddress the address authorized to receive the tokens + * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) + * @returns {string} signature authorizing withdrawing all earnings to given recipientAddress */ -export async function kick(dataUnionContractAddress, memberAddressList, provider) { - return sendToJoinPartStream(this, 'part', dataUnionContractAddress, memberAddressList, provider) +export async function signWithdrawTo(recipientAddress, options) { + return this.signWithdrawAmountTo(recipientAddress, BigNumber.from(0), options) } /** - * Add given Ethereum addresses as data union members - * @param {EthereumAddress} dataUnionContractAddress to manage - * @param {List} memberAddressList to kick - * @param {providers.Provider} provider (optional) default is mainnet + * Member can sign off to "donate" specific amount of earnings to another address such that someone else + * can submit the transaction (and pay for the gas) + * This signature is only valid until next withdrawal takes place (using this signature or otherwise). + * @param {EthereumAddress} recipientAddress the address authorized to receive the tokens + * @param {BigNumber|number|string} amountTokenWei that the signature is for (can't be used for less or for more) + * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) + * @returns {string} signature authorizing withdrawing all earnings to given recipientAddress */ -export async function addMembers(dataUnionContractAddress, memberAddressList, provider) { - return sendToJoinPartStream(this, 'join', dataUnionContractAddress, memberAddressList, provider) +export async function signWithdrawAmountTo(recipientAddress, amountTokenWei, options) { + const to = getAddress(recipientAddress) // throws if bad address + const signer = this.getSigner() // it shouldn't matter if it's mainnet or sidechain signer since key should be the same + const address = await signer.getAddress() + const duSidechain = await getSidechainContractReadOnly(this, options) + const memberData = await duSidechain.memberData(address) + if (memberData[0] === '0') { throw new Error(`${address} is not a member in Data Union (sidechain address ${duSidechain.address})`) } + const withdrawn = memberData[3] + const message = to + hexZeroPad(amountTokenWei, 32).slice(2) + duSidechain.address.slice(2) + hexZeroPad(withdrawn, 32).slice(2) + const signature = await signer.signMessage(arrayify(message)) + return signature } diff --git a/src/stream/Encryption.js b/src/stream/Encryption.js index 519d1f901..0726c880c 100644 --- a/src/stream/Encryption.js +++ b/src/stream/Encryption.js @@ -3,7 +3,7 @@ import util from 'util' // this is shimmed out for actual browser build allows us to run tests in node against browser API import { Crypto } from 'node-webcrypto-ossl' -import { ethers } from 'ethers' +import { arrayify, hexlify } from '@ethersproject/bytes' import { MessageLayer } from 'streamr-client-protocol' import { uuid } from '../utils' @@ -170,11 +170,11 @@ class EncryptionUtilBase { /* * Returns a Buffer or a hex String */ - static encryptWithPublicKey(plaintextBuffer, publicKey, hexlify = false) { + static encryptWithPublicKey(plaintextBuffer, publicKey, outputInHex = false) { this.validatePublicKey(publicKey) const ciphertextBuffer = crypto.publicEncrypt(publicKey, plaintextBuffer) - if (hexlify) { - return ethers.utils.hexlify(ciphertextBuffer).slice(2) + if (outputInHex) { + return hexlify(ciphertextBuffer).slice(2) } return ciphertextBuffer } @@ -186,7 +186,7 @@ class EncryptionUtilBase { GroupKey.validate(groupKey) const iv = crypto.randomBytes(16) // always need a fresh IV when using CTR mode const cipher = crypto.createCipheriv('aes-256-ctr', groupKey.data, iv) - return ethers.utils.hexlify(iv).slice(2) + cipher.update(data, null, 'hex') + cipher.final('hex') + return hexlify(iv).slice(2) + cipher.update(data, null, 'hex') + cipher.final('hex') } /* @@ -194,7 +194,7 @@ class EncryptionUtilBase { */ static decrypt(ciphertext, groupKey) { GroupKey.validate(groupKey) - const iv = ethers.utils.arrayify(`0x${ciphertext.slice(0, 32)}`) + const iv = arrayify(`0x${ciphertext.slice(0, 32)}`) const decipher = crypto.createDecipheriv('aes-256-ctr', groupKey.data, iv) return Buffer.concat([decipher.update(ciphertext.slice(32), 'hex', null), decipher.final(null)]) } @@ -282,7 +282,7 @@ export default class EncryptionUtil extends EncryptionUtilBase { if (!this.isReady()) { throw new Error('EncryptionUtil not ready.') } let ciphertextBuffer = ciphertext if (isHexString) { - ciphertextBuffer = ethers.utils.arrayify(`0x${ciphertext}`) + ciphertextBuffer = arrayify(`0x${ciphertext}`) } return crypto.privateDecrypt(this.privateKey, ciphertextBuffer) } diff --git a/src/stream/KeyExchange.js b/src/stream/KeyExchange.js index 47a7d7b29..da8f980ec 100644 --- a/src/stream/KeyExchange.js +++ b/src/stream/KeyExchange.js @@ -168,7 +168,7 @@ function waitForSubMessage(sub, matchFn) { async function subscribeToKeyExchangeStream(client, onKeyExchangeMessage) { const { options } = client - if ((!options.auth.privateKey && !options.auth.provider) || !options.keyExchange) { + if ((!options.auth.privateKey && !options.auth.ethereum) || !options.keyExchange) { return Promise.resolve() } diff --git a/src/utils/index.js b/src/utils/index.js index 0bc913167..22f6602b8 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -388,3 +388,31 @@ export async function allSettledValues(items, errorMessage = '') { return result.map(({ value }) => value) } + +export async function sleep(ms) { + return new Promise((resolve) => { + setTimeout(resolve, ms) + }) +} + +/** + * Wait until a condition is true + * @param {function(): Promise|function(): boolean} condition wait until this callback function returns true + * @param {number} [timeOutMs=10000] stop waiting after that many milliseconds, -1 for disable + * @param {number} [pollingIntervalMs=100] check condition between so many milliseconds + */ +export async function until(condition, timeOutMs = 10000, pollingIntervalMs = 100) { + let timeout = false + if (timeOutMs > 0) { + setTimeout(() => { timeout = true }, timeOutMs) + } + + // Promise wrapped condition function works for normal functions just the same as Promises + while (!await Promise.resolve().then(condition)) { // eslint-disable-line no-await-in-loop + if (timeout) { + throw new Error(`Timeout after ${timeOutMs} milliseconds`) + } + await sleep(pollingIntervalMs) // eslint-disable-line no-await-in-loop + } + return condition() +} diff --git a/test/browser/browser.js b/test/browser/browser.js index 8630f1163..c6f247278 100644 --- a/test/browser/browser.js +++ b/test/browser/browser.js @@ -36,7 +36,7 @@ describe('StreamrClient', () => { .verify.containsText('#result', '{"msg":8}') .verify.containsText('#result', '{"msg":9}') .assert.containsText('#result', '[{"msg":0},{"msg":1},{"msg":2},{"msg":3},{"msg":4},{"msg":5},{"msg":6},{"msg":7},{"msg":8},{"msg":9}]') - .pause(3000) + .pause(6000) .click('button[id=resend]') .pause(6000) .verify.containsText('#result', '{"msg":0}') diff --git a/test/flakey/DataUnionEndpoints.test.js b/test/flakey/DataUnionEndpoints.test.js deleted file mode 100644 index b9e21fcd2..000000000 --- a/test/flakey/DataUnionEndpoints.test.js +++ /dev/null @@ -1,284 +0,0 @@ -import { Contract, providers, utils, Wallet } from 'ethers' -import debug from 'debug' -import { wait } from 'streamr-test-utils' - -import StreamrClient from '../../src' -import * as Token from '../../contracts/TestToken.json' -import { getEndpointUrl } from '../../src/utils' -import authFetch from '../../src/rest/authFetch' -import config from '../integration/config' - -const log = debug('StreamrClient::DataUnionEndpoints::integration-test') - -/* eslint-disable no-await-in-loop */ - -describe('DataUnionEndPoints', () => { - let dataUnion - - let testProvider - let adminClient - let adminWallet - - const createProduct = async () => { - const DATA_UNION_VERSION = 1 - const properties = { - beneficiaryAddress: dataUnion.address, - type: 'DATAUNION', - dataUnionVersion: DATA_UNION_VERSION - } - const url = getEndpointUrl(config.clientOptions.restUrl, 'products') - return authFetch( - url, - adminClient.session, - { - method: 'POST', - body: JSON.stringify(properties) - } - ) - } - - beforeAll(async () => { - testProvider = new providers.JsonRpcProvider(config.ethereumServerUrl) - log(`Connecting to Ethereum network, config = ${JSON.stringify(config)}`) - - const network = await testProvider.getNetwork() - log('Connected to Ethereum network: ', JSON.stringify(network)) - - adminWallet = new Wallet(config.privateKey, testProvider) - adminClient = new StreamrClient({ - auth: { - privateKey: adminWallet.privateKey - }, - autoConnect: false, - autoDisconnect: false, - ...config.clientOptions, - }) - - log('beforeAll done') - }, 10000) - - beforeEach(async () => { - await adminClient.connect() - dataUnion = await adminClient.deployDataUnion({ - provider: testProvider, - }) - await dataUnion.deployed() - log(`Deployment done for ${dataUnion.address}`) - await dataUnion.isReady(2000, 200000) - log(`DataUnion ${dataUnion.address} is ready to roll`) - dataUnion.secret = await adminClient.createSecret(dataUnion.address, 'DataUnionEndpoints test secret') - await createProduct() - }, 300000) - - afterAll(async () => { - if (!adminClient) { return } - await adminClient.disconnect() - }) - - afterAll(async () => { - await testProvider.removeAllListeners() - }) - - describe('Admin', () => { - const memberAddressList = [ - '0x0000000000000000000000000000000000000001', - '0x0000000000000000000000000000000000000002', - '0x000000000000000000000000000000000000bEEF', - ] - - it('can add and remove members', async () => { - log('starting test') - await adminClient.dataUnionIsReady(dataUnion.address, log) - - await adminClient.addMembers(dataUnion.address, memberAddressList, testProvider) - await adminClient.hasJoined(dataUnion.address, memberAddressList[0]) - const res = await adminClient.getDataUnionStats(dataUnion.address) - expect(res.memberCount).toEqual({ - total: 3, active: 3, inactive: 0 - }) - - await adminClient.kick(dataUnion.address, memberAddressList.slice(1), testProvider) - await wait(1000) // TODO: instead of sleeping, find a way to check server has registered the parting - const res2 = await adminClient.getDataUnionStats(dataUnion.address) - expect(res2.memberCount).toEqual({ - total: 3, active: 1, inactive: 2 - }) - }, 300000) - - // separate test for adding and removing secrets? Adding secret is tested in member joins dataUnion test though. - }) - - describe('Members', () => { - let memberClient - const memberWallet = new Wallet('0x1000000000000000000000000000000000000000000000000000000000000001', testProvider) - - beforeAll(async () => { - memberClient = new StreamrClient({ - auth: { - privateKey: memberWallet.privateKey - }, - autoConnect: false, - autoDisconnect: false, - ...config.clientOptions, - }) - await memberClient.connect() - }) - - afterAll(async () => { - if (!memberClient) { return } - await memberClient.disconnect() - }) - - it('can join the dataUnion, and get their balances and stats, and check proof, and withdraw', async () => { - // send eth so the member can afford to send tx - await adminWallet.sendTransaction({ - to: memberWallet.address, - value: utils.parseEther('1'), - }) - - const res = await memberClient.joinDataUnion(dataUnion.address, dataUnion.secret) - await memberClient.hasJoined(dataUnion.address) - expect(res).toMatchObject({ - state: 'ACCEPTED', - memberAddress: memberWallet.address, - contractAddress: dataUnion.address, - }) - - // too much bother to check this in a separate test... TODO: split - const res2 = await memberClient.getMemberStats(dataUnion.address) - expect(res2).toEqual({ - active: true, - address: memberWallet.address, - earnings: '0', - recordedEarnings: '0', - withdrawableEarnings: '0', - frozenEarnings: '0' - }) - - // add revenue, just to see some action - const opWallet = new Wallet('0x5e98cce00cff5dea6b454889f359a4ec06b9fa6b88e9d69b86de8e1c81887da0', testProvider) - const opToken = new Contract(adminClient.options.tokenAddress, Token.abi, opWallet) - const tx = await opToken.mint(dataUnion.address, utils.parseEther('1')) - const tr = await tx.wait(2) - expect(tr.events[0].event).toBe('Transfer') - expect(tr.events[0].args.from).toBe('0x0000000000000000000000000000000000000000') - expect(tr.events[0].args.to).toBe(dataUnion.address) - expect(tr.events[0].args.value.toString()).toBe('1000000000000000000') - await wait(1000) - - // note: getMemberStats without explicit address => get stats of the authenticated StreamrClient - let res3 = await memberClient.getMemberStats(dataUnion.address) - while (!res3.withdrawableBlockNumber) { - await wait(4000) - res3 = await memberClient.getMemberStats(dataUnion.address) - } - expect(res3).toMatchObject({ - active: true, - address: memberWallet.address, - earnings: '1000000000000000000', - recordedEarnings: '1000000000000000000', - withdrawableEarnings: '1000000000000000000', - frozenEarnings: '0', - withdrawableBlockNumber: res3.withdrawableBlockNumber, - }) - - const isValid = await memberClient.validateProof(dataUnion.address, { - provider: testProvider - }) - expect(isValid).toBeTruthy() - - const walletBefore = await opToken.balanceOf(memberWallet.address) - await wait(80000) - const tr2 = await memberClient.withdraw(dataUnion.address, { - provider: testProvider - }) - expect(tr2.logs[0].address).toBe(adminClient.options.tokenAddress) - - const walletAfter = await opToken.balanceOf(memberWallet.address) - const diff = walletAfter.sub(walletBefore) - expect(diff.toString()).toBe(res3.withdrawableEarnings) - }, 600000) - - // TODO: test withdrawTo, withdrawFor, getBalance - }) - - describe('Anyone', () => { - const memberAddressList = [ - '0x0000000000000000000000000000000000000001', - '0x0000000000000000000000000000000000000002', - '0x000000000000000000000000000000000000bEEF', - ] - - let client - beforeAll(async () => { - client = new StreamrClient({ - auth: { - apiKey: 'tester1-api-key' - }, - autoConnect: false, - autoDisconnect: false, - ...config.clientOptions, - }) - }) - afterAll(async () => { - if (!client) { return } - await client.disconnect() - }) - - it('can get dataUnion stats, member list, and member stats', async () => { - await adminClient.addMembers(dataUnion.address, memberAddressList, testProvider) - await adminClient.hasJoined(dataUnion.address, memberAddressList[0]) - - // mint tokens to dataUnion to generate revenue - const opWallet = new Wallet('0x5e98cce00cff5dea6b454889f359a4ec06b9fa6b88e9d69b86de8e1c81887da0', testProvider) - const opToken = new Contract(adminClient.options.tokenAddress, Token.abi, opWallet) - const tx = await opToken.mint(dataUnion.address, utils.parseEther('1')) - const tr = await tx.wait(2) - expect(tr.events[0].event).toBe('Transfer') - expect(tr.events[0].args.from).toBe('0x0000000000000000000000000000000000000000') - expect(tr.events[0].args.to).toBe(dataUnion.address) - - await wait(1000) - let mstats = await client.getMemberStats(dataUnion.address, memberAddressList[0]) - while (!mstats.withdrawableBlockNumber) { - await wait(4000) - mstats = await client.getMemberStats(dataUnion.address, memberAddressList[0]) - } - - // TODO: clean up asserts - const cstats = await client.getDataUnionStats(dataUnion.address) - const mlist = await client.getMembers(dataUnion.address) - - expect(cstats.memberCount).toEqual({ - total: 3, active: 3, inactive: 0 - }) - expect(cstats.totalEarnings).toBe('1000000000000000000') - expect(cstats.latestWithdrawableBlock.memberCount).toBe(4) - expect(cstats.latestWithdrawableBlock.totalEarnings).toBe('1000000000000000000') - expect(mlist).toEqual([{ - active: true, - address: '0x0000000000000000000000000000000000000001', - earnings: '333333333333333333' - }, - { - active: true, - address: '0x0000000000000000000000000000000000000002', - earnings: '333333333333333333' - }, - { - active: true, - address: '0x000000000000000000000000000000000000bEEF', - earnings: '333333333333333333' - }]) - expect(mstats).toMatchObject({ - active: true, - address: '0x0000000000000000000000000000000000000001', - earnings: '333333333333333333', - recordedEarnings: '333333333333333333', - withdrawableEarnings: '333333333333333333', - frozenEarnings: '0', - withdrawableBlockNumber: cstats.latestWithdrawableBlock.blockNumber, - }) - }, 300000) - }) -}) diff --git a/test/integration/DataUnionEndpoints/DataUnionEndpoints.test.js b/test/integration/DataUnionEndpoints/DataUnionEndpoints.test.js new file mode 100644 index 000000000..1a6f7e504 --- /dev/null +++ b/test/integration/DataUnionEndpoints/DataUnionEndpoints.test.js @@ -0,0 +1,218 @@ +import { Contract, providers, Wallet } from 'ethers' +import { parseEther, formatEther } from 'ethers/lib/utils' +import { Mutex } from 'async-mutex' +import debug from 'debug' + +import { getEndpointUrl } from '../../../src/utils' +import authFetch from '../../../src/rest/authFetch' +import StreamrClient from '../../../src' +import * as Token from '../../../contracts/TestToken.json' +import config from '../config' + +const log = debug('StreamrClient::DataUnionEndpoints::integration-test') +// const log = console.log + +const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) +const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) + +describe('DataUnionEndPoints', () => { + let adminClient + + const tokenAdminWallet = new Wallet(config.tokenAdminPrivateKey, providerMainnet) + const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) + + afterAll(async () => { + await providerMainnet.removeAllListeners() + await providerSidechain.removeAllListeners() + await adminClient.ensureDisconnected() + }) + + const streamrClientCleanupList = [] + afterAll(async () => Promise.all(streamrClientCleanupList.map((c) => c.ensureDisconnected()))) + + beforeAll(async () => { + log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) + const network = await providerMainnet.getNetwork() + log('Connected to "mainnet" network: ', JSON.stringify(network)) + const network2 = await providerSidechain.getNetwork() + log('Connected to sidechain network: ', JSON.stringify(network2)) + + log(`Minting 100 tokens to ${adminWalletMainnet.address}`) + const tx1 = await tokenMainnet.mint(adminWalletMainnet.address, parseEther('100')) + await tx1.wait() + + adminClient = new StreamrClient(config.clientOptions) + await adminClient.ensureConnected() + }, 10000) + + // fresh dataUnion for each test case, created NOT in parallel to avoid nonce troubles + const adminMutex = new Mutex() + async function deployDataUnionSync(testName) { + let dataUnion + await adminMutex.runExclusive(async () => { + const dataUnionName = testName + Date.now() + log(`Starting deployment of dataUnionName=${dataUnionName}`) + dataUnion = await adminClient.deployDataUnion({ dataUnionName }) + log(`DataUnion ${dataUnion.address} is ready to roll`) + + // product is needed for join requests to analyze the DU version + const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') + await authFetch( + createProductUrl, + adminClient.session, + { + method: 'POST', + body: JSON.stringify({ + beneficiaryAddress: dataUnion.address, + type: 'DATAUNION', + dataUnionVersion: 2 + }) + } + ) + }) + return dataUnion + } + + describe('Admin', () => { + const memberAddressList = [ + '0x0000000000000000000000000000000000000001', + '0x0000000000000000000000000000000000000002', + '0x000000000000000000000000000000000000bEEF', + ] + + it('can add members', async () => { + const dataUnion = await deployDataUnionSync('add-members-test') + await adminMutex.runExclusive(async () => { + await adminClient.addMembers(memberAddressList, { dataUnion }) + await adminClient.hasJoined(memberAddressList[0], { dataUnion }) + }) + const res = await adminClient.getDataUnionStats({ dataUnion }) + expect(+res.activeMemberCount).toEqual(3) + expect(+res.inactiveMemberCount).toEqual(0) + }, 150000) + + it('can remove members', async () => { + const dataUnion = await deployDataUnionSync('remove-members-test') + await adminMutex.runExclusive(async () => { + await adminClient.addMembers(memberAddressList, { dataUnion }) + await adminClient.kick(memberAddressList.slice(1), { dataUnion }) + }) + const res = await adminClient.getDataUnionStats({ dataUnion }) + expect(+res.activeMemberCount).toEqual(1) + expect(+res.inactiveMemberCount).toEqual(2) + }, 150000) + + it('can set admin fee', async () => { + const dataUnion = await deployDataUnionSync('set-admin-fee-test') + const oldFee = await adminClient.getAdminFee({ dataUnion }) + await adminMutex.runExclusive(async () => { + log(`DU owner: ${await adminClient.getAdminAddress({ dataUnion })}`) + log(`Sending tx from ${adminClient.getAddress()}`) + const tr = await adminClient.setAdminFee(0.1, { dataUnion }) + log(`Transaction receipt: ${JSON.stringify(tr)}`) + }) + const newFee = await adminClient.getAdminFee({ dataUnion }) + expect(oldFee).toEqual(0) + expect(newFee).toEqual(0.1) + }, 150000) + + it('receives admin fees', async () => { + const dataUnion = await deployDataUnionSync('withdraw-admin-fees-test') + + await adminMutex.runExclusive(async () => { + await adminClient.addMembers(memberAddressList, { dataUnion }) + const tr = await adminClient.setAdminFee(0.1, { dataUnion }) + log(`Transaction receipt: ${JSON.stringify(tr)}`) + }) + + const amount = parseEther('2') + const tokenAddress = await dataUnion.token() + const adminTokenMainnet = new Contract(tokenAddress, Token.abi, adminWalletMainnet) + + await adminMutex.runExclusive(async () => { + log(`Transferring ${amount} token-wei ${adminWalletMainnet.address}->${dataUnion.address}`) + const txTokenToDU = await adminTokenMainnet.transfer(dataUnion.address, amount) + await txTokenToDU.wait() + }) + + const balance1 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance1)} (${balance1.toString()})`) + + log(`Transferred ${formatEther(amount)} tokens, next sending to bridge`) + const tx2 = await dataUnion.sendTokensToBridge() + await tx2.wait() + + const balance2 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance2)} (${balance2.toString()})`) + + expect(formatEther(balance2.sub(balance1))).toEqual('0.2') + }, 150000) + }) + + describe('Anyone', () => { + const nonce = Date.now() + const memberAddressList = [ + `0x100000000000000000000000000${nonce}`, + `0x200000000000000000000000000${nonce}`, + `0x300000000000000000000000000${nonce}`, + ] + + async function getOutsiderClient(dataUnion) { + const client = new StreamrClient({ + ...config.clientOptions, + auth: { + apiKey: 'tester1-api-key' + }, + dataUnion: dataUnion.address, + autoConnect: false, + autoDisconnect: false, + }) + streamrClientCleanupList.push(client) + return client + } + + it('can get dataUnion stats', async () => { + const dataUnion = await deployDataUnionSync('get-du-stats-test') + await adminMutex.runExclusive(async () => { + await adminClient.addMembers(memberAddressList, { dataUnion }) + }) + const client = await getOutsiderClient(dataUnion) + const stats = await client.getDataUnionStats() + expect(+stats.activeMemberCount).toEqual(3) + expect(+stats.inactiveMemberCount).toEqual(0) + expect(+stats.joinPartAgentCount).toEqual(2) + expect(+stats.totalEarnings).toEqual(0) + expect(+stats.totalWithdrawable).toEqual(0) + expect(+stats.lifetimeMemberEarnings).toEqual(0) + }, 150000) + + it('can get member stats', async () => { + const dataUnion = await deployDataUnionSync('get-member-stats-test') + await adminMutex.runExclusive(async () => { + await adminClient.addMembers(memberAddressList, { dataUnion }) + }) + const client = await getOutsiderClient(dataUnion) + const memberStats = await Promise.all(memberAddressList.map((m) => client.getMemberStats(m))) + expect(memberStats).toMatchObject([{ + status: 'active', + earningsBeforeLastJoin: '0', + lmeAtJoin: '0', + totalEarnings: '0', + withdrawableEarnings: '0', + }, { + status: 'active', + earningsBeforeLastJoin: '0', + lmeAtJoin: '0', + totalEarnings: '0', + withdrawableEarnings: '0', + }, { + status: 'active', + earningsBeforeLastJoin: '0', + lmeAtJoin: '0', + totalEarnings: '0', + withdrawableEarnings: '0', + }]) + }, 150000) + }) +}) diff --git a/test/integration/DataUnionEndpoints/adminWithdrawMember.test.js b/test/integration/DataUnionEndpoints/adminWithdrawMember.test.js new file mode 100644 index 000000000..bc29b2967 --- /dev/null +++ b/test/integration/DataUnionEndpoints/adminWithdrawMember.test.js @@ -0,0 +1,138 @@ +import { Contract, providers, Wallet } from 'ethers' +import { formatEther, parseEther } from 'ethers/lib/utils' +import debug from 'debug' + +import { getEndpointUrl, until } from '../../../src/utils' +import StreamrClient from '../../../src' +import * as Token from '../../../contracts/TestToken.json' +import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' +import config from '../config' +import authFetch from '../../../src/rest/authFetch' + +const log = debug('StreamrClient::DataUnionEndpoints::integration-test-withdrawTo') +// const { log } = console + +const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) +const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) +const adminWalletSidechain = new Wallet(config.clientOptions.auth.privateKey, providerSidechain) + +const tokenAdminWallet = new Wallet(config.tokenAdminPrivateKey, providerMainnet) +const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) + +it('DataUnionEndPoints test withdraw from admin', async () => { + log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) + const network = await providerMainnet.getNetwork() + log('Connected to "mainnet" network: ', JSON.stringify(network)) + const network2 = await providerSidechain.getNetwork() + log('Connected to sidechain network: ', JSON.stringify(network2)) + + log(`Minting 100 tokens to ${adminWalletMainnet.address}`) + const tx1 = await tokenMainnet.mint(adminWalletMainnet.address, parseEther('100')) + await tx1.wait() + + const adminClient = new StreamrClient(config.clientOptions) + await adminClient.ensureConnected() + + const dataUnion = await adminClient.deployDataUnion() + const secret = await adminClient.createSecret(dataUnion.address, 'DataUnionEndpoints test secret') + log(`DataUnion ${dataUnion.address} is ready to roll`) + // dataUnion = await adminClient.getDataUnionContract({dataUnion: "0xd778CfA9BB1d5F36E42526B2BAFD07B74b4066c0"}) + + const memberWallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) + const sendTx = await adminWalletSidechain.sendTransaction({ to: memberWallet.address, value: parseEther('0.1') }) + await sendTx.wait() + log(`sent 0.1sETH to ${memberWallet.address}`) + + const memberClient = new StreamrClient({ + ...config.clientOptions, + auth: { + privateKey: memberWallet.privateKey + }, + dataUnion: dataUnion.address, + }) + await memberClient.ensureConnected() + + // product is needed for join requests to analyze the DU version + const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') + await authFetch(createProductUrl, adminClient.session, { + method: 'POST', + body: JSON.stringify({ + beneficiaryAddress: dataUnion.address, + type: 'DATAUNION', + dataUnionVersion: 2 + }) + }) + const res = await memberClient.joinDataUnion({ secret }) + // await adminClient.addMembers([memberWallet.address], { dataUnion }) + log(`Member joined data union: ${JSON.stringify(res)}`) + + const tokenAddress = await dataUnion.token() + log(`Token address: ${tokenAddress}`) + const adminTokenMainnet = new Contract(tokenAddress, Token.abi, adminWalletMainnet) + + const amount = parseEther('1') + const duSidechainEarningsBefore = await dataUnion.sidechain.totalEarnings() + + const duBalance1 = await adminTokenMainnet.balanceOf(dataUnion.address) + log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance1)} (${duBalance1.toString()})`) + const balance1 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance1)} (${balance1.toString()})`) + + log(`Transferring ${amount} token-wei ${adminWalletMainnet.address}->${dataUnion.address}`) + const txTokenToDU = await adminTokenMainnet.transfer(dataUnion.address, amount) + await txTokenToDU.wait() + + const duBalance2 = await adminTokenMainnet.balanceOf(dataUnion.address) + log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance2)} (${duBalance2.toString()})`) + const balance2 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance2)} (${balance2.toString()})`) + + log(`DU member count: ${await dataUnion.sidechain.activeMemberCount()}`) + + log(`Transferred ${formatEther(amount)} tokens, next sending to bridge`) + const tx2 = await dataUnion.sendTokensToBridge() + await tx2.wait() + + log(`Sent to bridge, waiting for the tokens to appear at ${dataUnion.sidechain.address} in sidechain`) + const tokenSidechain = new Contract(config.clientOptions.tokenAddressSidechain, Token.abi, adminWalletSidechain) + await until(async () => !(await tokenSidechain.balanceOf(dataUnion.sidechain.address)).eq('0'), 300000, 3000) + log(`Confirmed tokens arrived, DU balance: ${duSidechainEarningsBefore} -> ${await dataUnion.sidechain.totalEarnings()}`) + + const sidechainContract = new Contract(dataUnion.sidechain.address, DataUnionSidechain.abi, adminWalletSidechain) + const tx3 = await sidechainContract.refreshRevenue() + const tr3 = await tx3.wait() + log(`refreshRevenue returned ${JSON.stringify(tr3)}`) + log(`DU balance: ${await dataUnion.sidechain.totalEarnings()}`) + + const duBalance3 = await adminTokenMainnet.balanceOf(dataUnion.address) + log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance3)} (${duBalance3.toString()})`) + const balance3 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance3)} (${balance3.toString()})`) + + // note: getMemberStats without explicit address => get stats of the authenticated StreamrClient + const stats = await memberClient.getMemberStats() + log(`stats ${JSON.stringify(stats)}`) + + const balanceBefore = await adminTokenMainnet.balanceOf(memberWallet.address) + log(`balanceBefore ${balanceBefore}. Withdrawing tokens...`) + const withdrawTr = await adminClient.withdrawMember(memberWallet.address, { dataUnion }) + log(`Tokens withdrawn, sidechain tx receipt: ${JSON.stringify(withdrawTr)}`) + const balanceAfter = await adminTokenMainnet.balanceOf(memberWallet.address) + const balanceIncrease = balanceAfter.sub(balanceBefore) + + await providerMainnet.removeAllListeners() + await providerSidechain.removeAllListeners() + await memberClient.ensureDisconnected() + await adminClient.ensureDisconnected() + + expect(stats).toMatchObject({ + status: 'active', + earningsBeforeLastJoin: '0', + lmeAtJoin: '0', + totalEarnings: '1000000000000000000', + withdrawableEarnings: '1000000000000000000', + }) + expect(withdrawTr.logs[0].address).toBe(config.clientOptions.tokenAddressSidechain) + expect(balanceIncrease.toString()).toBe(amount.toString()) +}, 300000) diff --git a/test/integration/DataUnionEndpoints/adminWithdrawSigned.test.js b/test/integration/DataUnionEndpoints/adminWithdrawSigned.test.js new file mode 100644 index 000000000..9d018e4de --- /dev/null +++ b/test/integration/DataUnionEndpoints/adminWithdrawSigned.test.js @@ -0,0 +1,155 @@ +import { Contract, providers, Wallet } from 'ethers' +import { formatEther, parseEther } from 'ethers/lib/utils' +import debug from 'debug' + +import { getEndpointUrl, until } from '../../../src/utils' +import StreamrClient from '../../../src' +import * as Token from '../../../contracts/TestToken.json' +import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' +import config from '../config' +import authFetch from '../../../src/rest/authFetch' + +const log = debug('StreamrClient::DataUnionEndpoints::integration-test-withdraw') +// const { log } = console + +const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) +const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) +const adminWalletSidechain = new Wallet(config.clientOptions.auth.privateKey, providerSidechain) + +const tokenAdminWallet = new Wallet(config.tokenAdminPrivateKey, providerMainnet) +const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) + +it('DataUnionEndPoints test signed withdraw from admin', async () => { + log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) + const network = await providerMainnet.getNetwork() + log('Connected to "mainnet" network: ', JSON.stringify(network)) + const network2 = await providerSidechain.getNetwork() + log('Connected to sidechain network: ', JSON.stringify(network2)) + + log(`Minting 100 tokens to ${adminWalletMainnet.address}`) + const tx1 = await tokenMainnet.mint(adminWalletMainnet.address, parseEther('100')) + await tx1.wait() + + const adminClient = new StreamrClient(config.clientOptions) + await adminClient.ensureConnected() + + const dataUnion = await adminClient.deployDataUnion() + const secret = await adminClient.createSecret(dataUnion.address, 'DataUnionEndpoints test secret') + log(`DataUnion ${dataUnion.address} is ready to roll`) + // dataUnion = await adminClient.getDataUnionContract({dataUnion: "0xd778CfA9BB1d5F36E42526B2BAFD07B74b4066c0"}) + + const memberWallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) + const member2Wallet = new Wallet(`0x100000000000000000000000000000000000000012300000002${Date.now()}`, providerSidechain) + const sendTx = await adminWalletSidechain.sendTransaction({ to: memberWallet.address, value: parseEther('0.1') }) + await sendTx.wait() + log(`sent 0.1sETH to ${memberWallet.address}`) + + const memberClient = new StreamrClient({ + ...config.clientOptions, + auth: { + privateKey: memberWallet.privateKey + }, + dataUnion: dataUnion.address, + }) + await memberClient.ensureConnected() + + // product is needed for join requests to analyze the DU version + const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') + await authFetch(createProductUrl, adminClient.session, { + method: 'POST', + body: JSON.stringify({ + beneficiaryAddress: dataUnion.address, + type: 'DATAUNION', + dataUnionVersion: 2 + }) + }) + await memberClient.joinDataUnion({ secret }) + // await adminClient.addMembers([memberWallet.address], { dataUnion }) + + const tokenAddress = await dataUnion.token() + log(`Token address: ${tokenAddress}`) + const adminTokenMainnet = new Contract(tokenAddress, Token.abi, adminWalletMainnet) + + const amount = parseEther('1') + const duSidechainEarningsBefore = await dataUnion.sidechain.totalEarnings() + + const duBalance1 = await adminTokenMainnet.balanceOf(dataUnion.address) + log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance1)} (${duBalance1.toString()})`) + const balance1 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance1)} (${balance1.toString()})`) + + log(`Transferring ${amount} token-wei ${adminWalletMainnet.address}->${dataUnion.address}`) + const txTokenToDU = await adminTokenMainnet.transfer(dataUnion.address, amount) + await txTokenToDU.wait() + + const duBalance2 = await adminTokenMainnet.balanceOf(dataUnion.address) + log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance2)} (${duBalance2.toString()})`) + const balance2 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance2)} (${balance2.toString()})`) + + log(`DU member count: ${await dataUnion.sidechain.activeMemberCount()}`) + + log(`Transferred ${formatEther(amount)} tokens, next sending to bridge`) + const tx2 = await dataUnion.sendTokensToBridge() + await tx2.wait() + + log(`Sent to bridge, waiting for the tokens to appear at ${dataUnion.sidechain.address} in sidechain`) + const tokenSidechain = new Contract(config.clientOptions.tokenAddressSidechain, Token.abi, adminWalletSidechain) + await until(async () => !(await tokenSidechain.balanceOf(dataUnion.sidechain.address)).eq('0'), 300000, 3000) + log(`Confirmed tokens arrived, DU balance: ${duSidechainEarningsBefore} -> ${await dataUnion.sidechain.totalEarnings()}`) + + // make a "full" sidechain contract object that has all functions, not just those required by StreamrClient + const sidechainContract = new Contract(dataUnion.sidechain.address, DataUnionSidechain.abi, adminWalletSidechain) + const tx3 = await sidechainContract.refreshRevenue() + const tr3 = await tx3.wait() + log(`refreshRevenue returned ${JSON.stringify(tr3)}`) + log(`DU balance: ${await dataUnion.sidechain.totalEarnings()}`) + + const duBalance3 = await adminTokenMainnet.balanceOf(dataUnion.address) + log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance3)} (${duBalance3.toString()})`) + const balance3 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance3)} (${balance3.toString()})`) + + // note: getMemberStats without explicit address => get stats of the authenticated StreamrClient + const stats = await memberClient.getMemberStats() + log(`Stats: ${JSON.stringify(stats)}. Withdrawing tokens...`) + + // try different ways of signing, for coverage; TODO: separate into own test + const signature = await memberClient.signWithdrawTo(member2Wallet.address) + const signature2 = await memberClient.signWithdrawAmountTo(member2Wallet.address, parseEther('1')) + const signature3 = await memberClient.signWithdrawAmountTo(member2Wallet.address, 3000000000000000) // 0.003 tokens + + const isValid = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, '0', signature) // '0' = all earnings + const isValid2 = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, parseEther('1'), signature2) + const isValid3 = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, '3000000000000000', signature3) + log(`Signature for all tokens ${memberWallet.address} -> ${member2Wallet.address}: ${signature}, checked ${isValid ? 'OK' : '!!!BROKEN!!!'}`) + log(`Signature for 1 token ${memberWallet.address} -> ${member2Wallet.address}: ${signature2}, checked ${isValid2 ? 'OK' : '!!!BROKEN!!!'}`) + log(`Signature for 0.003 tokens ${memberWallet.address} -> ${member2Wallet.address}: ${signature3}, checked ${isValid3 ? 'OK' : '!!!BROKEN!!!'}`) + log(`sidechainDU(${sidechainContract.address}) token bal ${await tokenSidechain.balanceOf(sidechainContract.address)}`) + + const balanceBefore = await adminTokenMainnet.balanceOf(member2Wallet.address) + log(`balanceBefore ${balanceBefore}. Withdrawing tokens...`) + const withdrawTr = await adminClient.withdrawToSigned(memberWallet.address, member2Wallet.address, signature, { dataUnion }) + log(`Tokens withdrawn, sidechain tx receipt: ${JSON.stringify(withdrawTr)}`) + const balanceAfter = await adminTokenMainnet.balanceOf(member2Wallet.address) + const balanceIncrease = balanceAfter.sub(balanceBefore) + + await providerMainnet.removeAllListeners() + await providerSidechain.removeAllListeners() + await memberClient.ensureDisconnected() + await adminClient.ensureDisconnected() + + expect(stats).toMatchObject({ + status: 'active', + earningsBeforeLastJoin: '0', + lmeAtJoin: '0', + totalEarnings: '1000000000000000000', + withdrawableEarnings: '1000000000000000000', + }) + expect(withdrawTr.logs[0].address).toBe(config.clientOptions.tokenAddressSidechain) + expect(balanceIncrease.toString()).toBe(amount.toString()) + expect(isValid).toBe(true) + expect(isValid2).toBe(true) + expect(isValid3).toBe(true) +}, 300000) diff --git a/test/integration/DataUnionEndpoints/calculate.test.js b/test/integration/DataUnionEndpoints/calculate.test.js new file mode 100644 index 000000000..0fcc5e96d --- /dev/null +++ b/test/integration/DataUnionEndpoints/calculate.test.js @@ -0,0 +1,39 @@ +import { providers, Wallet } from 'ethers' +import debug from 'debug' + +import StreamrClient from '../../../src' +import config from '../config' + +const log = debug('StreamrClient::DataUnionEndpoints::integration-test-calculate') +// const { log } = console + +const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) +const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) + +it('DataUnionEndPoints: calculate DU address before deployment', async () => { + log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) + const network = await providerMainnet.getNetwork() + log('Connected to "mainnet" network: ', JSON.stringify(network)) + const network2 = await providerSidechain.getNetwork() + log('Connected to sidechain network: ', JSON.stringify(network2)) + + const adminClient = new StreamrClient(config.clientOptions) + await adminClient.ensureConnected() + + const dataUnionName = '6be8ceda7a3c4fe7991eab501975b85ec2bb90452d0e4c93bc2' + Date.now() + const duMainnetAddress = await adminClient.calculateDataUnionMainnetAddress(dataUnionName, adminWalletMainnet.address) + const duSidechainAddress = await adminClient.calculateDataUnionSidechainAddress(duMainnetAddress) + + const dataUnion = await adminClient.deployDataUnion({ dataUnionName }) + + const version = await adminClient.getDataUnionVersion(dataUnion.address) + + await providerMainnet.removeAllListeners() + await providerSidechain.removeAllListeners() + await adminClient.ensureDisconnected() + + expect(duMainnetAddress).toBe(dataUnion.address) + expect(duSidechainAddress).toBe(dataUnion.sidechain.address) + expect(version).toBe(2) +}, 60000) diff --git a/test/integration/DataUnionEndpoints/withdraw.test.js b/test/integration/DataUnionEndpoints/withdraw.test.js new file mode 100644 index 000000000..cf7b28624 --- /dev/null +++ b/test/integration/DataUnionEndpoints/withdraw.test.js @@ -0,0 +1,140 @@ +import { Contract, providers, Wallet } from 'ethers' +import { formatEther, parseEther } from 'ethers/lib/utils' +import debug from 'debug' + +import { getEndpointUrl, until } from '../../../src/utils' +import StreamrClient from '../../../src' +import * as Token from '../../../contracts/TestToken.json' +import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' +import config from '../config' +import authFetch from '../../../src/rest/authFetch' + +const log = debug('StreamrClient::DataUnionEndpoints::integration-test-withdraw') +// const { log } = console + +const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) +const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) +const adminWalletSidechain = new Wallet(config.clientOptions.auth.privateKey, providerSidechain) + +const tokenAdminWallet = new Wallet(config.tokenAdminPrivateKey, providerMainnet) +const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) + +it('DataUnionEndPoints test withdraw by member itself', async () => { + log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) + const network = await providerMainnet.getNetwork() + log('Connected to "mainnet" network: ', JSON.stringify(network)) + const network2 = await providerSidechain.getNetwork() + log('Connected to sidechain network: ', JSON.stringify(network2)) + + log(`Minting 100 tokens to ${adminWalletMainnet.address}`) + const tx1 = await tokenMainnet.mint(adminWalletMainnet.address, parseEther('100')) + await tx1.wait() + + const adminClient = new StreamrClient(config.clientOptions) + await adminClient.ensureConnected() + + const dataUnion = await adminClient.deployDataUnion() + const secret = await adminClient.createSecret(dataUnion.address, 'DataUnionEndpoints test secret') + log(`DataUnion ${dataUnion.address} is ready to roll`) + // dataUnion = await adminClient.getDataUnionContract({dataUnion: "0xd778CfA9BB1d5F36E42526B2BAFD07B74b4066c0"}) + + const memberWallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) + const sendTx = await adminWalletSidechain.sendTransaction({ to: memberWallet.address, value: parseEther('0.1') }) + await sendTx.wait() + log(`Sent 0.1 sidechain-ETH to ${memberWallet.address}`) + + const send2Tx = await adminWalletMainnet.sendTransaction({ to: memberWallet.address, value: parseEther('0.1') }) + await send2Tx.wait() + log(`Sent 0.1 mainnet-ETH to ${memberWallet.address}`) + + const memberClient = new StreamrClient({ + ...config.clientOptions, + auth: { + privateKey: memberWallet.privateKey + }, + dataUnion: dataUnion.address, + }) + await memberClient.ensureConnected() + + // product is needed for join requests to analyze the DU version + const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') + await authFetch(createProductUrl, adminClient.session, { + method: 'POST', + body: JSON.stringify({ + beneficiaryAddress: dataUnion.address, + type: 'DATAUNION', + dataUnionVersion: 2 + }) + }) + await memberClient.joinDataUnion({ secret }) + // await adminClient.addMembers([memberWallet.address], { dataUnion }) + + const tokenAddress = await dataUnion.token() + log(`Token address: ${tokenAddress}`) + const adminTokenMainnet = new Contract(tokenAddress, Token.abi, adminWalletMainnet) + + const amount = parseEther('1') + const duSidechainEarningsBefore = await dataUnion.sidechain.totalEarnings() + + const duBalance1 = await adminTokenMainnet.balanceOf(dataUnion.address) + log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance1)} (${duBalance1.toString()})`) + const balance1 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance1)} (${balance1.toString()})`) + + log(`Transferring ${amount} token-wei ${adminWalletMainnet.address}->${dataUnion.address}`) + const txTokenToDU = await adminTokenMainnet.transfer(dataUnion.address, amount) + await txTokenToDU.wait() + + const duBalance2 = await adminTokenMainnet.balanceOf(dataUnion.address) + log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance2)} (${duBalance2.toString()})`) + const balance2 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance2)} (${balance2.toString()})`) + + log(`DU member count: ${await dataUnion.sidechain.activeMemberCount()}`) + + log(`Transferred ${formatEther(amount)} tokens, next sending to bridge`) + const tx2 = await dataUnion.sendTokensToBridge() + await tx2.wait() + + log(`Sent to bridge, waiting for the tokens to appear at ${dataUnion.sidechain.address} in sidechain`) + const tokenSidechain = new Contract(config.clientOptions.tokenAddressSidechain, Token.abi, adminWalletSidechain) + await until(async () => !(await tokenSidechain.balanceOf(dataUnion.sidechain.address)).eq('0'), 300000, 3000) + log(`Confirmed tokens arrived, DU balance: ${duSidechainEarningsBefore} -> ${await dataUnion.sidechain.totalEarnings()}`) + + const sidechainContract = new Contract(dataUnion.sidechain.address, DataUnionSidechain.abi, adminWalletSidechain) + const tx3 = await sidechainContract.refreshRevenue() + const tr3 = await tx3.wait() + log(`refreshRevenue returned ${JSON.stringify(tr3)}`) + log(`DU balance: ${await dataUnion.sidechain.totalEarnings()}`) + + const duBalance3 = await adminTokenMainnet.balanceOf(dataUnion.address) + log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance3)} (${duBalance3.toString()})`) + const balance3 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance3)} (${balance3.toString()})`) + + // note: getMemberStats without explicit address => get stats of the authenticated StreamrClient + const stats = await memberClient.getMemberStats() + log(`Stats: ${JSON.stringify(stats)}. Withdrawing tokens...`) + + const balanceBefore = await adminTokenMainnet.balanceOf(memberWallet.address) + const withdrawTr = await memberClient.withdraw() + log(`Tokens withdrawn, sidechain tx receipt: ${JSON.stringify(withdrawTr)}`) + const balanceAfter = await adminTokenMainnet.balanceOf(memberWallet.address) + const balanceIncrease = balanceAfter.sub(balanceBefore) + + await providerMainnet.removeAllListeners() + await providerSidechain.removeAllListeners() + await memberClient.ensureDisconnected() + await adminClient.ensureDisconnected() + + expect(stats).toMatchObject({ + status: 'active', + earningsBeforeLastJoin: '0', + lmeAtJoin: '0', + totalEarnings: '1000000000000000000', + withdrawableEarnings: '1000000000000000000', + }) + expect(withdrawTr.logs[0].address).toBe(config.clientOptions.tokenAddressSidechain) + expect(balanceIncrease.toString()).toBe(amount.toString()) +}, 300000) diff --git a/test/integration/DataUnionEndpoints/withdrawTo.test.js b/test/integration/DataUnionEndpoints/withdrawTo.test.js new file mode 100644 index 000000000..bfabe7fed --- /dev/null +++ b/test/integration/DataUnionEndpoints/withdrawTo.test.js @@ -0,0 +1,142 @@ +import { Contract, providers, Wallet } from 'ethers' +import { formatEther, parseEther } from 'ethers/lib/utils' +import debug from 'debug' + +import { getEndpointUrl, until } from '../../../src/utils' +import StreamrClient from '../../../src' +import * as Token from '../../../contracts/TestToken.json' +import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' +import config from '../config' +import authFetch from '../../../src/rest/authFetch' + +const log = debug('StreamrClient::DataUnionEndpoints::integration-test-withdrawTo') +// const { log } = console + +const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) +const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) +const adminWalletSidechain = new Wallet(config.clientOptions.auth.privateKey, providerSidechain) + +const tokenAdminWallet = new Wallet(config.tokenAdminPrivateKey, providerMainnet) +const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) + +it('DataUnionEndPoints test withdrawTo from member to any address', async () => { + log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) + const network = await providerMainnet.getNetwork() + log('Connected to "mainnet" network: ', JSON.stringify(network)) + const network2 = await providerSidechain.getNetwork() + log('Connected to sidechain network: ', JSON.stringify(network2)) + + log(`Minting 100 tokens to ${adminWalletMainnet.address}`) + const tx1 = await tokenMainnet.mint(adminWalletMainnet.address, parseEther('100')) + await tx1.wait() + + const adminClient = new StreamrClient(config.clientOptions) + await adminClient.ensureConnected() + + const dataUnion = await adminClient.deployDataUnion() + const secret = await adminClient.createSecret(dataUnion.address, 'DataUnionEndpoints test secret') + log(`DataUnion ${dataUnion.address} is ready to roll`) + // dataUnion = await adminClient.getDataUnionContract({dataUnion: "0xd778CfA9BB1d5F36E42526B2BAFD07B74b4066c0"}) + + const memberWallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) + const outsiderWallet = new Wallet(`0x100000000000000000000000000000000000000012300000002${Date.now()}`, providerSidechain) + const sendTx = await adminWalletSidechain.sendTransaction({ to: memberWallet.address, value: parseEther('0.1') }) + await sendTx.wait() + log(`Sent 0.1 sidechain-ETH to ${memberWallet.address}`) + + const send2Tx = await adminWalletMainnet.sendTransaction({ to: memberWallet.address, value: parseEther('0.1') }) + await send2Tx.wait() + log(`Sent 0.1 mainnet-ETH to ${memberWallet.address}`) + + const memberClient = new StreamrClient({ + ...config.clientOptions, + auth: { + privateKey: memberWallet.privateKey + }, + dataUnion: dataUnion.address, + }) + await memberClient.ensureConnected() + + // product is needed for join requests to analyze the DU version + const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') + await authFetch(createProductUrl, adminClient.session, { + method: 'POST', + body: JSON.stringify({ + beneficiaryAddress: dataUnion.address, + type: 'DATAUNION', + dataUnionVersion: 2 + }) + }) + await memberClient.joinDataUnion({ secret }) + // await adminClient.addMembers([memberWallet.address], { dataUnion }) + + const tokenAddress = await dataUnion.token() + log(`Token address: ${tokenAddress}`) + const adminTokenMainnet = new Contract(tokenAddress, Token.abi, adminWalletMainnet) + + const amount = parseEther('1') + const duSidechainEarningsBefore = await dataUnion.sidechain.totalEarnings() + + const duBalance1 = await adminTokenMainnet.balanceOf(dataUnion.address) + log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance1)} (${duBalance1.toString()})`) + const balance1 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance1)} (${balance1.toString()})`) + + log(`Transferring ${amount} token-wei ${adminWalletMainnet.address}->${dataUnion.address}`) + const txTokenToDU = await adminTokenMainnet.transfer(dataUnion.address, amount) + await txTokenToDU.wait() + + const duBalance2 = await adminTokenMainnet.balanceOf(dataUnion.address) + log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance2)} (${duBalance2.toString()})`) + const balance2 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance2)} (${balance2.toString()})`) + + log(`DU member count: ${await dataUnion.sidechain.activeMemberCount()}`) + + log(`Transferred ${formatEther(amount)} tokens, next sending to bridge`) + const tx2 = await dataUnion.sendTokensToBridge() + await tx2.wait() + + log(`Sent to bridge, waiting for the tokens to appear at ${dataUnion.sidechain.address} in sidechain`) + const tokenSidechain = new Contract(config.clientOptions.tokenAddressSidechain, Token.abi, adminWalletSidechain) + await until(async () => !(await tokenSidechain.balanceOf(dataUnion.sidechain.address)).eq('0'), 300000, 3000) + log(`Confirmed tokens arrived, DU balance: ${duSidechainEarningsBefore} -> ${await dataUnion.sidechain.totalEarnings()}`) + + const sidechainContract = new Contract(dataUnion.sidechain.address, DataUnionSidechain.abi, adminWalletSidechain) + const tx3 = await sidechainContract.refreshRevenue() + const tr3 = await tx3.wait() + log(`refreshRevenue returned ${JSON.stringify(tr3)}`) + log(`DU balance: ${await dataUnion.sidechain.totalEarnings()}`) + + const duBalance3 = await adminTokenMainnet.balanceOf(dataUnion.address) + log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance3)} (${duBalance3.toString()})`) + const balance3 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance3)} (${balance3.toString()})`) + + // note: getMemberStats without explicit address => get stats of the authenticated StreamrClient + const stats = await memberClient.getMemberStats() + log(`stats ${JSON.stringify(stats)}`) + + const balanceBefore = await adminTokenMainnet.balanceOf(outsiderWallet.address) + log(`balanceBefore ${balanceBefore}. Withdrawing tokens...`) + const withdrawTr = await memberClient.withdrawTo(outsiderWallet.address) + log(`Tokens withdrawn, sidechain tx receipt: ${JSON.stringify(withdrawTr)}`) + const balanceAfter = await adminTokenMainnet.balanceOf(outsiderWallet.address) + const balanceIncrease = balanceAfter.sub(balanceBefore) + + await providerMainnet.removeAllListeners() + await providerSidechain.removeAllListeners() + await memberClient.ensureDisconnected() + await adminClient.ensureDisconnected() + + expect(stats).toMatchObject({ + status: 'active', + earningsBeforeLastJoin: '0', + lmeAtJoin: '0', + totalEarnings: '1000000000000000000', + withdrawableEarnings: '1000000000000000000', + }) + expect(withdrawTr.logs[0].address).toBe(config.clientOptions.tokenAddressSidechain) + expect(balanceIncrease.toString()).toBe(amount.toString()) +}, 300000) diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.js index 29e2d08f6..f86696f29 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.js @@ -123,13 +123,15 @@ function TestStreamEndpoints(getName) { describe('getStreamPublishers', () => { it('retrieves a list of publishers', async () => { const publishers = await client.getStreamPublishers(createdStream.id) - expect(publishers).toEqual([(await client.getPublisherId()).toLowerCase()]) + const address = await client.signer.getAddress() + expect(publishers).toEqual([address.toLowerCase()]) }) }) describe('isStreamPublisher', () => { it('returns true for valid publishers', async () => { - const valid = await client.isStreamPublisher(createdStream.id, await client.getPublisherId()) + const address = await client.signer.getAddress() + const valid = await client.isStreamPublisher(createdStream.id, address.toLowerCase()) expect(valid).toBeTruthy() }) it('returns false for invalid publishers', async () => { @@ -141,13 +143,15 @@ function TestStreamEndpoints(getName) { describe('getStreamSubscribers', () => { it('retrieves a list of publishers', async () => { const subscribers = await client.getStreamSubscribers(createdStream.id) - expect(subscribers).toEqual([(await client.getPublisherId()).toLowerCase()]) + const address = await client.signer.getAddress() + expect(subscribers).toEqual([address.toLowerCase()]) }) }) describe('isStreamSubscriber', () => { it('returns true for valid subscribers', async () => { - const valid = await client.isStreamSubscriber(createdStream.id, (await client.getPublisherId()).toLowerCase()) + const address = await client.signer.getAddress() + const valid = await client.isStreamSubscriber(createdStream.id, address.toLowerCase()) expect(valid).toBeTruthy() }) it('returns false for invalid subscribers', async () => { diff --git a/test/integration/config.js b/test/integration/config.js index 27550eb05..4c6328d5c 100644 --- a/test/integration/config.js +++ b/test/integration/config.js @@ -1,12 +1,25 @@ module.exports = { clientOptions: { + // ganache 1: 0x4178baBE9E5148c6D5fd431cD72884B07Ad855a0 + auth: { + privateKey: process.env.ETHEREUM_PRIVATE_KEY || '0xe5af7834455b7239881b85be89d905d6881dcb4751063897f12be1b0dd546bdb', + }, url: process.env.WEBSOCKET_URL || 'ws://localhost/api/v1/ws', restUrl: process.env.REST_URL || 'http://localhost/api/v1', + streamrNodeAddress: '0xFCAd0B19bB29D4674531d6f115237E16AfCE377c', tokenAddress: process.env.TOKEN_ADDRESS || '0xbAA81A0179015bE47Ad439566374F2Bae098686F', - streamrNodeAddress: process.env.STREAMR_NODE_ADDRESS || '0xFCAd0B19bB29D4674531d6f115237E16AfCE377c', - streamrOperatorAddress: process.env.OPERATOR_ADDRESS || '0xa3d1F77ACfF0060F7213D7BF3c7fEC78df847De1', + tokenAddressSidechain: process.env.TOKEN_ADDRESS_SIDECHAIN || '0x73Be21733CC5D08e1a14Ea9a399fb27DB3BEf8fF', + factoryMainnetAddress: process.env.DU_FACTORY_MAINNET || '0x5E959e5d5F3813bE5c6CeA996a286F734cc9593b', + sidechain: { + url: process.env.SIDECHAIN_URL || 'http://10.200.10.1:8546', + timeout: process.env.TEST_TIMEOUT, + }, + mainnet: { + url: process.env.ETHEREUM_SERVER_URL || 'http://10.200.10.1:8545', + timeout: process.env.TEST_TIMEOUT, + }, + autoConnect: false, + autoDisconnect: false, }, - ethereumServerUrl: process.env.ETHEREUM_SERVER_URL || 'http://localhost:8545', - // ganache 1: 0x4178baBE9E5148c6D5fd431cD72884B07Ad855a0 - privateKey: process.env.ETHEREUM_PRIVATE_KEY || '0xe5af7834455b7239881b85be89d905d6881dcb4751063897f12be1b0dd546bdb', + tokenAdminPrivateKey: '0x5e98cce00cff5dea6b454889f359a4ec06b9fa6b88e9d69b86de8e1c81887da0', } diff --git a/test/legacy/SubscribedStreamPartition.test.js b/test/legacy/SubscribedStreamPartition.test.js index f31b0539f..ba91dbe80 100644 --- a/test/legacy/SubscribedStreamPartition.test.js +++ b/test/legacy/SubscribedStreamPartition.test.js @@ -147,7 +147,7 @@ describe('SubscribedStreamPartition', () => { } const timestamp = Date.now() const msg = new StreamMessage({ - messageId: new MessageIDStrict(streamId, 0, timestamp, 0, signer.address, ''), + messageId: new MessageIDStrict(streamId, 0, timestamp, 0, await signer.getAddress(), ''), prevMesssageRef: null, content: data, messageType: StreamMessage.MESSAGE_TYPES.MESSAGE, diff --git a/test/unit/Session.test.js b/test/unit/Session.test.js index 1f529ce05..0004faab6 100644 --- a/test/unit/Session.test.js +++ b/test/unit/Session.test.js @@ -61,7 +61,7 @@ describe('Session', () => { await expect(async () => ( clientSessionToken.session.loginFunction() )).rejects.toThrow( - 'Need either "privateKey", "provider", "apiKey", "username"+"password" or "sessionToken" to login.' + 'Need either "privateKey", "ethereum", "apiKey", "username"+"password" or "sessionToken" to login.' ) }) @@ -70,11 +70,17 @@ describe('Session', () => { auth: {}, }) clientNone.onError = () => {} + await clientNone.session.loginFunction().catch((err) => { + expect(err.toString()).toEqual( + 'Error: Need either "privateKey", "ethereum", "apiKey", "username"+"password" or "sessionToken" to login.' + ) + }) + clientNone.onError = () => {} await expect(async () => ( clientSessionToken.session.loginFunction() )).rejects.toThrow( - 'Need either "privateKey", "provider", "apiKey", "username"+"password" or "sessionToken" to login.' + 'Need either "privateKey", "ethereum", "apiKey", "username"+"password" or "sessionToken" to login.' ) }) }) @@ -121,7 +127,7 @@ describe('Session', () => { beforeEach(() => { session = new Session() session.options.unauthenticated = false - msg = 'Error: Need either "privateKey", "provider", "apiKey" or "username"+"password" to login.' + msg = 'Error: Need either "privateKey", "ethereum", "apiKey" or "username"+"password" to login.' session.loginFunction = sinon.stub().rejects(new Error(msg)) clientSessionToken.onError = () => {} }) diff --git a/test/unit/Signer.test.js b/test/unit/Signer.test.js index 04ba05e78..116d9a5bc 100644 --- a/test/unit/Signer.test.js +++ b/test/unit/Signer.test.js @@ -81,8 +81,9 @@ describe('Signer', () => { }) it('should sign StreamMessageV31 with null previous ref correctly', async () => { + const address = await signer.getAddress() const streamMessage = new StreamMessage({ - messageId: new MessageID(streamId, 0, timestamp, 0, signer.address, 'chain-id'), + messageId: new MessageID(streamId, 0, timestamp, 0, address, 'chain-id'), prevMsgRef: null, content: data, encryptionType: StreamMessage.ENCRYPTION_TYPES.NONE, @@ -90,20 +91,21 @@ describe('Signer', () => { signature: null }) const payload = streamMessage.getStreamId() + streamMessage.getStreamPartition() + streamMessage.getTimestamp() - + streamMessage.messageId.sequenceNumber + signer.address.toLowerCase() + streamMessage.messageId.msgChainId + + streamMessage.messageId.sequenceNumber + address.toLowerCase() + streamMessage.messageId.msgChainId + streamMessage.getSerializedContent() const expectedSignature = await signer.signData(payload) await signer(streamMessage) expect(streamMessage.signature).toBe(expectedSignature) - expect(streamMessage.getPublisherId()).toBe(signer.address) + expect(streamMessage.getPublisherId()).toBe(address) expect(streamMessage.signatureType).toBe(StreamMessage.SIGNATURE_TYPES.ETH) }) it('should sign StreamMessageV31 with non-null previous ref correctly', async () => { + const address = await signer.getAddress() const streamMessage = new StreamMessage({ version: 31, - messageId: new MessageID(streamId, 0, timestamp, 0, signer.address, 'chain-id'), + messageId: new MessageID(streamId, 0, timestamp, 0, address, 'chain-id'), prevMsgRef: new MessageRef(timestamp - 10, 0), content: data, encryptionType: StreamMessage.ENCRYPTION_TYPES.NONE, @@ -112,7 +114,7 @@ describe('Signer', () => { }) const payload = [ streamMessage.getStreamId(), streamMessage.getStreamPartition(), streamMessage.getTimestamp(), - streamMessage.messageId.sequenceNumber, signer.address.toLowerCase(), streamMessage.messageId.msgChainId, + streamMessage.messageId.sequenceNumber, address.toLowerCase(), streamMessage.messageId.msgChainId, streamMessage.prevMsgRef.timestamp, streamMessage.prevMsgRef.sequenceNumber, streamMessage.getSerializedContent() ] const expectedSignature = await signer.signData(payload.join('')) @@ -120,7 +122,7 @@ describe('Signer', () => { expect(expectedSignature).toEqual(await signer.signData(streamMessage.getPayloadToSign())) await signer(streamMessage) expect(streamMessage.signature).toBe(expectedSignature) - expect(streamMessage.getPublisherId()).toBe(signer.address) + expect(streamMessage.getPublisherId()).toBe(address) expect(streamMessage.signatureType).toBe(StreamMessage.SIGNATURE_TYPES.ETH) }) }) From 67c629fc175451bb05a3f8afad7a1c8a533d14e0 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 6 Jan 2021 13:39:03 -0500 Subject: [PATCH 354/517] Use integration/config.js as defaults, allow tests to override. --- test/integration/Encryption.test.js | 4 +++- test/integration/LoginEndpoints.test.js | 2 +- test/integration/MultipleClients.test.js | 2 +- test/integration/ResendReconnect.test.js | 8 ++++---- test/integration/Resends.test.js | 2 +- test/integration/Sequencing.test.js | 2 +- test/integration/StreamConnectionState.test.js | 2 +- test/integration/StreamEndpoints.test.js | 2 +- test/integration/StreamrClient.test.js | 2 +- test/integration/Subscriber.test.js | 2 +- test/integration/SubscriberResends.test.js | 2 +- test/integration/Subscription.test.js | 2 +- test/integration/Validation.test.js | 2 +- test/unit/Session.test.js | 2 +- 14 files changed, 19 insertions(+), 17 deletions(-) diff --git a/test/integration/Encryption.test.js b/test/integration/Encryption.test.js index 0dce65490..bd0d08341 100644 --- a/test/integration/Encryption.test.js +++ b/test/integration/Encryption.test.js @@ -12,6 +12,7 @@ import config from './config' const TIMEOUT = 30 * 1000 const { StreamMessage } = MessageLayer + describe('decryption', () => { let publishTestMessages let expectErrors = 0 // check no errors by default @@ -27,6 +28,7 @@ describe('decryption', () => { const createClient = (opts = {}) => { const c = new StreamrClient({ + ...config.clientOptions, auth: { privateKey: fakePrivateKey(), }, @@ -35,7 +37,6 @@ describe('decryption', () => { disconnectDelay: 1, publishAutoDisconnectDelay: 50, maxRetries: 2, - ...config.clientOptions, ...opts, }) c.onError = jest.fn() @@ -191,6 +192,7 @@ describe('decryption', () => { autoConnect: true, autoDisconnect: true, }) + const onEncryptionMessageErr = checkEncryptionMessages(client) const onEncryptionMessageErr2 = checkEncryptionMessages(otherClient) const otherUser = await otherClient.getUserInfo() diff --git a/test/integration/LoginEndpoints.test.js b/test/integration/LoginEndpoints.test.js index 674fbd479..7ceb8213a 100644 --- a/test/integration/LoginEndpoints.test.js +++ b/test/integration/LoginEndpoints.test.js @@ -10,10 +10,10 @@ describe('LoginEndpoints', () => { let client const createClient = (opts = {}) => new StreamrClient({ + ...config.clientOptions, apiKey: 'tester1-api-key', autoConnect: false, autoDisconnect: false, - ...config.clientOptions, ...opts, }) diff --git a/test/integration/MultipleClients.test.js b/test/integration/MultipleClients.test.js index f60d6f81d..a98fe586a 100644 --- a/test/integration/MultipleClients.test.js +++ b/test/integration/MultipleClients.test.js @@ -7,12 +7,12 @@ import Connection from '../../src/Connection' import config from './config' const createClient = (opts = {}) => new StreamrClient({ + ...config.clientOptions, auth: { privateKey: fakePrivateKey() }, autoConnect: false, autoDisconnect: false, - ...config.clientOptions, ...opts, }) diff --git a/test/integration/ResendReconnect.test.js b/test/integration/ResendReconnect.test.js index 3130a1822..22031a99c 100644 --- a/test/integration/ResendReconnect.test.js +++ b/test/integration/ResendReconnect.test.js @@ -7,15 +7,15 @@ import { Defer } from '../../src/utils' import config from './config' const createClient = (opts = {}) => new StreamrClient({ + ...(config.clientOptions || { + url: config.websocketUrl, + restUrl: config.restUrl, + }), auth: { privateKey: fakePrivateKey(), }, autoConnect: false, autoDisconnect: false, - ...(config.clientOptions || { - url: config.websocketUrl, - restUrl: config.restUrl, - }), ...opts, }) diff --git a/test/integration/Resends.test.js b/test/integration/Resends.test.js index 3ee7ff017..6c9914d5b 100644 --- a/test/integration/Resends.test.js +++ b/test/integration/Resends.test.js @@ -19,13 +19,13 @@ describe('StreamrClient resends', () => { const createClient = (opts = {}) => { const c = new StreamrClient({ + ...config.clientOptions, auth: { privateKey: fakePrivateKey(), }, autoConnect: false, autoDisconnect: false, maxRetries: 2, - ...config.clientOptions, ...opts, }) c.onError = jest.fn() diff --git a/test/integration/Sequencing.test.js b/test/integration/Sequencing.test.js index 47014589f..4c5800d16 100644 --- a/test/integration/Sequencing.test.js +++ b/test/integration/Sequencing.test.js @@ -29,13 +29,13 @@ describe('Sequencing', () => { const createClient = (opts = {}) => { const c = new StreamrClient({ + ...config.clientOptions, auth: { privateKey: fakePrivateKey(), }, autoConnect: false, autoDisconnect: false, maxRetries: 2, - ...config.clientOptions, ...opts, }) c.onError = jest.fn() diff --git a/test/integration/StreamConnectionState.test.js b/test/integration/StreamConnectionState.test.js index 5dbee605e..48c324a65 100644 --- a/test/integration/StreamConnectionState.test.js +++ b/test/integration/StreamConnectionState.test.js @@ -21,6 +21,7 @@ describeRepeats('Connection State', () => { const createClient = (opts = {}) => { const c = new StreamrClient({ + ...config.clientOptions, auth: { privateKey: fakePrivateKey(), }, @@ -28,7 +29,6 @@ describeRepeats('Connection State', () => { autoDisconnect: false, publishAutoDisconnectDelay: 250, maxRetries: 2, - ...config.clientOptions, ...opts, }) c.onError = jest.fn() diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.js index f86696f29..6f04e5d9f 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.js @@ -16,9 +16,9 @@ function TestStreamEndpoints(getName) { let createdStream const createClient = (opts = {}) => new StreamrClient({ + ...config.clientOptions, autoConnect: false, autoDisconnect: false, - ...config.clientOptions, ...opts, }) diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index ff5e632b5..3b83c5dc3 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -32,6 +32,7 @@ describeRepeats('StreamrClient', () => { const createClient = (opts = {}) => { const c = new StreamrClient({ + ...config.clientOptions, auth: { privateKey: fakePrivateKey(), }, @@ -40,7 +41,6 @@ describeRepeats('StreamrClient', () => { disconnectDelay: 1, publishAutoDisconnectDelay: 50, maxRetries: 2, - ...config.clientOptions, ...opts, }) c.onError = jest.fn() diff --git a/test/integration/Subscriber.test.js b/test/integration/Subscriber.test.js index 89c4dbadf..76529aa45 100644 --- a/test/integration/Subscriber.test.js +++ b/test/integration/Subscriber.test.js @@ -22,13 +22,13 @@ describeRepeats('StreamrClient Stream', () => { const createClient = (opts = {}) => { const c = new StreamrClient({ + ...config.clientOptions, auth: { privateKey: fakePrivateKey(), }, autoConnect: false, autoDisconnect: false, maxRetries: 2, - ...config.clientOptions, ...opts, }) c.onError = jest.fn() diff --git a/test/integration/SubscriberResends.test.js b/test/integration/SubscriberResends.test.js index 8ed12b388..59054f3a7 100644 --- a/test/integration/SubscriberResends.test.js +++ b/test/integration/SubscriberResends.test.js @@ -28,6 +28,7 @@ describeRepeats('resends', () => { const createClient = (opts = {}) => { const c = new StreamrClient({ + ...config.clientOptions, auth: { privateKey: fakePrivateKey(), }, @@ -35,7 +36,6 @@ describeRepeats('resends', () => { autoConnect: false, autoDisconnect: false, maxRetries: 2, - ...config.clientOptions, ...opts, }) c.onError = jest.fn() diff --git a/test/integration/Subscription.test.js b/test/integration/Subscription.test.js index 7f07f9609..7f1ee0312 100644 --- a/test/integration/Subscription.test.js +++ b/test/integration/Subscription.test.js @@ -6,12 +6,12 @@ import StreamrClient from '../../src' import config from './config' const createClient = (opts = {}) => new StreamrClient({ + ...config.clientOptions, auth: { privateKey: fakePrivateKey(), }, autoConnect: false, autoDisconnect: false, - ...config.clientOptions, ...opts, }) diff --git a/test/integration/Validation.test.js b/test/integration/Validation.test.js index 44ed082d0..f1454da05 100644 --- a/test/integration/Validation.test.js +++ b/test/integration/Validation.test.js @@ -18,13 +18,13 @@ describeRepeats('GapFill', () => { const createClient = (opts = {}) => { const c = new StreamrClient({ + ...config.clientOptions, auth: { privateKey: fakePrivateKey(), }, autoConnect: false, autoDisconnect: false, maxRetries: 2, - ...config.clientOptions, ...opts, }) c.onError = jest.fn() diff --git a/test/unit/Session.test.js b/test/unit/Session.test.js index 0004faab6..9610f5797 100644 --- a/test/unit/Session.test.js +++ b/test/unit/Session.test.js @@ -11,9 +11,9 @@ describe('Session', () => { let clientSessionToken const createClient = (opts = {}) => new StreamrClient({ + ...config.clientOptions, autoConnect: false, autoDisconnect: false, - ...config.clientOptions, ...opts, }) From c7d5694d8b22269ff3b517f331d10de7a5799dee Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 6 Jan 2021 13:40:53 -0500 Subject: [PATCH 355/517] Rename getPublisherId to getUserId. Move into src/user. --- src/StreamrClient.js | 18 +++++---- src/publish/Signer.js | 50 ++++++++++-------------- src/publish/index.js | 47 +--------------------- src/stream/KeyExchange.js | 2 +- src/user/index.js | 50 ++++++++++++++++++++++++ test/integration/StreamEndpoints.test.js | 16 ++++---- test/integration/StreamrClient.test.js | 2 +- test/unit/Signer.test.js | 12 +++--- 8 files changed, 99 insertions(+), 98 deletions(-) create mode 100644 src/user/index.js diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 07679d078..080ba43c5 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -11,11 +11,9 @@ import Session from './Session' import Connection from './Connection' import Publisher from './publish' import Subscriber from './subscribe' +import { getUserId } from './user' -const { - JsonRpcProvider, - Web3Provider -} = providers +const { JsonRpcProvider } = providers /** * Wrap connection message events with message parsing. @@ -80,7 +78,7 @@ class StreamrCached { } }) - this.getPublisherId = CacheAsyncFn(client.getPublisherId.bind(client), cacheOptions) + this.getUserId = CacheAsyncFn(client.getUserId.bind(client), cacheOptions) } clearStream(streamId) { @@ -91,6 +89,12 @@ class StreamrCached { clearUser() { this.getUserInfo.clear() + this.getUserId.clear() + } + + clear() { + this.clearUser() + this.clearStream() } } @@ -249,8 +253,8 @@ export default class StreamrClient extends EventEmitter { return this.publisher.publish(...args) } - getPublisherId() { - return this.publisher.getPublisherId() + async getUserId() { + return getUserId(this) } setNextGroupKey(...args) { diff --git a/src/publish/Signer.js b/src/publish/Signer.js index 0f0061254..8f28c7a03 100644 --- a/src/publish/Signer.js +++ b/src/publish/Signer.js @@ -1,11 +1,27 @@ import { MessageLayer, Utils } from 'streamr-client-protocol' -import { computeAddress } from '@ethersproject/transactions' import { Web3Provider } from '@ethersproject/providers' const { StreamMessage } = MessageLayer const { SigningUtil } = Utils const { SIGNATURE_TYPES } = StreamMessage +function getSigningFunction({ privateKey, ethereum } = {}) { + if (privateKey) { + const key = (typeof privateKey === 'string' && privateKey.startsWith('0x')) + ? privateKey.slice(2) // strip leading 0x + : privateKey + return async (d) => SigningUtil.sign(d, key) + } + + if (ethereum) { + const web3Provider = new Web3Provider(ethereum) + const signer = web3Provider.getSigner() + return async (d) => signer.signMessage(d) + } + + throw new Error('Need either "privateKey" or "ethereum".') +} + export default function Signer(options = {}, publishWithSignature = 'auto') { const { privateKey, ethereum } = options @@ -21,35 +37,11 @@ export default function Signer(options = {}, publishWithSignature = 'auto') { throw new Error(`Unknown parameter value: ${publishWithSignature}`) } - let address - let sign - let getAddress = async () => {} - if (privateKey) { - address = computeAddress(privateKey) - const key = (typeof privateKey === 'string' && privateKey.startsWith('0x')) - ? privateKey.slice(2) // strip leading 0x - : privateKey - getAddress = async () => address - sign = async (d) => { - return SigningUtil.sign(d, key) - } - } else if (ethereum) { - const web3Provider = new Web3Provider(ethereum) - const signer = web3Provider.getSigner() - getAddress = async () => { - if (address) { return address } - // eslint-disable-next-line require-atomic-updates - address = await signer.getAddress() - return address - } - sign = async (d) => signer.signMessage(d) - } else { - throw new Error('Need either "privateKey" or "provider".') - } + const sign = getSigningFunction(options) async function signStreamMessage(streamMessage, signatureType = SIGNATURE_TYPES.ETH) { if (!streamMessage) { - throw new Error('streamMessage required as part of the data to sign.') + throw new Error('streamMessage required as part of the data to sign.') } if (typeof streamMessage.getTimestamp !== 'function' || !streamMessage.getTimestamp()) { @@ -69,8 +61,6 @@ export default function Signer(options = {}, publishWithSignature = 'auto') { } return Object.assign(signStreamMessage, { - // these mainly for tests - signData: sign, - getAddress, + signData: sign, // this mainly for tests }) } diff --git a/src/publish/index.js b/src/publish/index.js index 472abea41..292065a08 100644 --- a/src/publish/index.js +++ b/src/publish/index.js @@ -2,10 +2,6 @@ import { inspect } from 'util' import crypto from 'crypto' import { ControlLayer, MessageLayer } from 'streamr-client-protocol' -import { hexlify } from '@ethersproject/bytes' -import { computeAddress } from '@ethersproject/transactions' -import { Web3Provider } from '@ethersproject/providers' -import { sha256 } from '@ethersproject/sha2' import mem from 'mem' import { uuid, CacheFn, LimitAsyncFnByKey, randomString } from '../utils' @@ -110,44 +106,6 @@ function StreamPartitioner(cacheOptions) { return computeStreamPartition } -async function getUsername(client) { - const { options: { auth = {} } = {} } = client - if (auth.username) { return auth.username } - - const { username, id } = await client.cached.getUserInfo() - return ( - username - // edge case: if auth.apiKey is an anonymous key, userInfo.id is that anonymous key - || id - ) -} - -async function getPublisherId(client) { - if (client.session.isUnauthenticated()) { - throw new Error('Need to be authenticated to getPublisherId.') - } - - const { options: { auth = {} } = {} } = client - if (auth.privateKey) { - return computeAddress(auth.privateKey).toLowerCase() - } - - if (auth.provider) { - const provider = new Web3Provider(auth.ethereum) - const address = (await provider.getSigner().getAddress()).toLowerCase() - return address - } - - const username = await getUsername(client) - - if (username != null) { - const hexString = hexlify(Buffer.from(await this.getUsername(), 'utf8')) - return sha256(hexString) - } - - throw new Error('Need either "privateKey", "ethereum", "apiKey", "username"+"password" or "sessionToken" to derive the publisher Id.') -} - /* * Get function for creating stream messages. */ @@ -192,7 +150,7 @@ function getCreateStreamMessage(client) { // load cached stream + publisher details const [stream, publisherId] = await Promise.all([ client.cached.getStream(streamId), - client.cached.getPublisherId(client), + client.cached.getUserId(client), ]) // figure out partition @@ -346,9 +304,6 @@ export default function Publisher(client) { sendQueue.clear() createStreamMessage.clear() }, - async getPublisherId() { - return getPublisherId(client) - }, rotateGroupKey(streamId) { return createStreamMessage.rotateGroupKey(streamId) }, diff --git a/src/stream/KeyExchange.js b/src/stream/KeyExchange.js index da8f980ec..b95f64c6b 100644 --- a/src/stream/KeyExchange.js +++ b/src/stream/KeyExchange.js @@ -174,7 +174,7 @@ async function subscribeToKeyExchangeStream(client, onKeyExchangeMessage) { await client.session.getSessionToken() // trigger auth errors if any // subscribing to own keyexchange stream - const publisherId = await client.getPublisherId() + const publisherId = await client.getUserId() const streamId = getKeyExchangeStreamId(publisherId) return client.subscribe(streamId, onKeyExchangeMessage) } diff --git a/src/user/index.js b/src/user/index.js new file mode 100644 index 000000000..4f0679cdb --- /dev/null +++ b/src/user/index.js @@ -0,0 +1,50 @@ +import { computeAddress } from '@ethersproject/transactions' +import { Web3Provider } from '@ethersproject/providers' +import { hexlify } from '@ethersproject/bytes' +import { sha256 } from '@ethersproject/sha2' + +async function getUsername(client) { + const { options: { auth = {} } = {} } = client + if (auth.username) { return auth.username } + + const { username, id } = await client.cached.getUserInfo() + return ( + username + // edge case: if auth.apiKey is an anonymous key, userInfo.id is that anonymous key + || id + ) +} + +export async function getAddressFromOptions({ ethereum, privateKey } = {}) { + if (privateKey) { + return computeAddress(privateKey).toLowerCase() + } + + if (ethereum) { + const provider = new Web3Provider(ethereum) + const address = await provider.getSigner().getAddress() + return address.toLowerCase() + } + + throw new Error('Need either "privateKey" or "ethereum".') +} + +export async function getUserId(client) { + if (client.session.isUnauthenticated()) { + throw new Error('Need to be authenticated to getUserId.') + } + + const { options: { auth = {} } = {} } = client + if (auth.ethereum || auth.privateKey) { + return getAddressFromOptions(auth) + } + + const username = await getUsername(client) + + if (username != null) { + const hexString = hexlify(Buffer.from(await this.getUsername(), 'utf8')) + return sha256(hexString) + } + + throw new Error('Need either "privateKey", "ethereum", "apiKey", "username"+"password" or "sessionToken" to derive the publisher Id.') +} diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.js index 6f04e5d9f..ca5b11da7 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.js @@ -123,15 +123,15 @@ function TestStreamEndpoints(getName) { describe('getStreamPublishers', () => { it('retrieves a list of publishers', async () => { const publishers = await client.getStreamPublishers(createdStream.id) - const address = await client.signer.getAddress() - expect(publishers).toEqual([address.toLowerCase()]) + const address = await client.getUserId() + expect(publishers).toEqual([address]) }) }) describe('isStreamPublisher', () => { it('returns true for valid publishers', async () => { - const address = await client.signer.getAddress() - const valid = await client.isStreamPublisher(createdStream.id, address.toLowerCase()) + const address = await client.getUserId() + const valid = await client.isStreamPublisher(createdStream.id, address) expect(valid).toBeTruthy() }) it('returns false for invalid publishers', async () => { @@ -143,15 +143,15 @@ function TestStreamEndpoints(getName) { describe('getStreamSubscribers', () => { it('retrieves a list of publishers', async () => { const subscribers = await client.getStreamSubscribers(createdStream.id) - const address = await client.signer.getAddress() - expect(subscribers).toEqual([address.toLowerCase()]) + const address = await client.getUserId() + expect(subscribers).toEqual([address]) }) }) describe('isStreamSubscriber', () => { it('returns true for valid subscribers', async () => { - const address = await client.signer.getAddress() - const valid = await client.isStreamSubscriber(createdStream.id, address.toLowerCase()) + const address = await client.getUserId() + const valid = await client.isStreamSubscriber(createdStream.id, address) expect(valid).toBeTruthy() }) it('returns false for invalid subscribers', async () => { diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index 3b83c5dc3..1f82319b4 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -693,7 +693,7 @@ describeRepeats('StreamrClient', () => { }) it('is stream publisher', async () => { - const publisherId = await client.getPublisherId() + const publisherId = await client.getUserId() const res = await client.isStreamPublisher(stream.id, publisherId) expect(res).toBe(true) }) diff --git a/test/unit/Signer.test.js b/test/unit/Signer.test.js index 116d9a5bc..0751cdb20 100644 --- a/test/unit/Signer.test.js +++ b/test/unit/Signer.test.js @@ -1,6 +1,7 @@ import { MessageLayer } from 'streamr-client-protocol' import Signer from '../../src/publish/Signer' +import { getAddressFromOptions } from '../../src/user' const { StreamMessage, MessageID, MessageRef } = MessageLayer /* @@ -66,11 +67,12 @@ describe('Signer', () => { field: 'some-data', } const timestamp = 1529549961116 + const options = { + privateKey: '0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709', + } beforeEach(() => { - signer = Signer({ - privateKey: '0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709', - }) + signer = Signer(options) }) it('should return correct signature', async () => { @@ -81,7 +83,7 @@ describe('Signer', () => { }) it('should sign StreamMessageV31 with null previous ref correctly', async () => { - const address = await signer.getAddress() + const address = await getAddressFromOptions(options) const streamMessage = new StreamMessage({ messageId: new MessageID(streamId, 0, timestamp, 0, address, 'chain-id'), prevMsgRef: null, @@ -102,7 +104,7 @@ describe('Signer', () => { }) it('should sign StreamMessageV31 with non-null previous ref correctly', async () => { - const address = await signer.getAddress() + const address = await getAddressFromOptions(options) const streamMessage = new StreamMessage({ version: 31, messageId: new MessageID(streamId, 0, timestamp, 0, address, 'chain-id'), From 4973ba168c88523249ede594bef7849302de6c91 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 18 Jan 2021 17:01:00 -0500 Subject: [PATCH 356/517] Ensure Session options object is never shared across clients. Prevents session token corruption. --- src/Session.js | 7 +++++-- test/integration/Session.test.js | 17 +++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/Session.js b/src/Session.js index 624dca32d..d4c5eaad6 100644 --- a/src/Session.js +++ b/src/Session.js @@ -3,10 +3,13 @@ import { Wallet } from '@ethersproject/wallet' import { Web3Provider } from '@ethersproject/providers' export default class Session extends EventEmitter { - constructor(client, options) { + constructor(client, options = {}) { super() this._client = client - this.options = options || {} + this.options = { + ...options + } + this.state = Session.State.LOGGED_OUT // TODO: move loginFunction to StreamrClient constructor where "auth type" is checked diff --git a/test/integration/Session.test.js b/test/integration/Session.test.js index a6c638a3f..d20264505 100644 --- a/test/integration/Session.test.js +++ b/test/integration/Session.test.js @@ -41,6 +41,23 @@ describe('Session', () => { }).session.getSessionToken()).resolves.toBeTruthy() }) + it('can handle multiple client instances', async () => { + expect.assertions(1) + const client1 = createClient({ + auth: { + privateKey: fakePrivateKey(), + }, + }) + const client2 = createClient({ + auth: { + privateKey: fakePrivateKey(), + }, + }) + const token1 = await client1.session.getSessionToken() + const token2 = await client2.session.getSessionToken() + expect(token1).not.toEqual(token2) + }) + it('fails if trying to get the token using username and password', async () => { expect.assertions(1) await expect(() => createClient({ From c0d47c067672d726c42cc79b1b793bb26b08f13c Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 6 Jan 2021 13:49:12 -0500 Subject: [PATCH 357/517] Fix up CI yaml. --- .github/workflows/nodejs.yml | 37 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 457ea6873..eeaf5a26c 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -73,7 +73,7 @@ jobs: fail-fast: false matrix: node-version: [12.x, 14.x] - test-name: ['test-integration-no-resend', 'test-integration-resend'] + test-name: ["test-integration-no-resend", "test-integration-resend"] env: TEST_NAME: ${{ matrix.test-name }} @@ -87,8 +87,8 @@ jobs: - uses: actions/checkout@v2 with: repository: streamr-dev/streamr-docker-dev - path: streamr-dev - - name: setup streamr-dev + path: streamr-docker-dev + - name: setup streamr-docker-dev run: | sudo service mysql stop sudo ifconfig docker0 10.200.10.1/24 @@ -114,8 +114,8 @@ jobs: - uses: actions/checkout@v2 with: repository: streamr-dev/streamr-docker-dev - path: streamr-dev - - name: setup streamr-dev + path: streamr-docker-dev + - name: setup streamr-docker-dev run: | sudo service mysql stop sudo ifconfig docker0 10.200.10.1/24 @@ -146,8 +146,8 @@ jobs: - uses: actions/checkout@v2 with: repository: streamr-dev/streamr-docker-dev - path: streamr-dev - - name: setup streamr-dev + path: streamr-docker-dev + - name: setup streamr-docker-dev run: | sudo service mysql stop sudo ifconfig docker0 10.200.10.1/24 @@ -176,8 +176,8 @@ jobs: - uses: actions/checkout@v2 with: repository: streamr-dev/streamr-docker-dev - path: streamr-dev - - name: setup streamr-dev + path: streamr-docker-dev + - name: setup streamr-docker-dev run: | sudo service mysql stop sudo ifconfig docker0 10.200.10.1/24 @@ -212,9 +212,9 @@ jobs: - js-realtime-only - java-realtime-only env: - NUM_MESSAGES: 10 - TEST_NAME: ${{ matrix.test-name }} - CONFIG_NAME: ${{ matrix.config-name }} + NUM_MESSAGES: 10 + TEST_NAME: ${{ matrix.test-name }} + CONFIG_NAME: ${{ matrix.config-name }} steps: - uses: actions/checkout@v2 - name: Use Node.js 14 @@ -228,8 +228,8 @@ jobs: - uses: actions/checkout@v2 with: repository: streamr-dev/streamr-docker-dev - path: streamr-dev - - name: setup-docker + path: streamr-docker-dev + - name: setup streamr-docker-dev run: | sudo service mysql stop sudo ifconfig docker0 10.200.10.1/24 @@ -243,14 +243,7 @@ jobs: - name: setup-client-testing working-directory: streamr-client-testing run: | - #echo "Setup package.json to use PR JS client" - #sed -i "s/com.streamr:client:1.3.0/com.streamr:client:+/g" build.gradle - #sed -i "s/\"streamr-client\": \"latest\"/\"streamr-client\":\"PATH\"/g" package.json - ## fix for escaping / of workspace path - #sed -i "s|"PATH"|"${GITHUB_WORKSPACE}"|g" package.json - #echo "Prepare for test" - #cat package.json - ## npm install is used because package-lock.json could be form a previous client version. + ## npm install is used because package-lock.json could be from a previous client version. npm install npm link streamr-client ./gradlew fatjar From 24ba1fa6785cbe2c22dc7ed129b17692ae3dd01c Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 7 Jan 2021 08:47:22 -0500 Subject: [PATCH 358/517] Fix up CI yaml. --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index eeaf5a26c..ac741a692 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -233,7 +233,7 @@ jobs: run: | sudo service mysql stop sudo ifconfig docker0 10.200.10.1/24 - ${GITHUB_WORKSPACE}/streamr-dev/streamr-docker-dev/bin.sh start nginx broker-node-storage-1 broker-node-no-storage-1 broker-node-no-storage-2 engine-and-editor tracker-1 --wait + ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start nginx broker-node-storage-1 broker-node-no-storage-1 broker-node-no-storage-2 engine-and-editor tracker-1 --wait - name: npm link run: npm ci && npm link - uses: actions/checkout@v2 From ce112a93d6dee0c945225146babb2b916d193564 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 7 Jan 2021 08:47:46 -0500 Subject: [PATCH 359/517] Separate dataunion tests in CI. --- .github/workflows/nodejs.yml | 2 +- package.json | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index ac741a692..ae703beaa 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -73,7 +73,7 @@ jobs: fail-fast: false matrix: node-version: [12.x, 14.x] - test-name: ["test-integration-no-resend", "test-integration-resend"] + test-name: ["test-integration-no-resend", "test-integration-resend", "test-integration-dataunions"] env: TEST_NAME: ${{ matrix.test-name }} diff --git a/package.json b/package.json index 883fc4fd5..56610d0ad 100644 --- a/package.json +++ b/package.json @@ -26,8 +26,9 @@ "test-unit": "jest test/unit --detectOpenHandles", "coverage": "jest --coverage", "test-integration": "jest --forceExit test/integration", - "test-integration-no-resend": "jest --testPathIgnorePatterns='resend|Resend' --testNamePattern='^((?!(resend|Resend|resent|Resent)).)*$' test/integration", - "test-integration-resend": "jest --testTimeout=15000 --testNamePattern='(resend|Resend|resent|Resent)' test/integration", + "test-integration-no-resend": "jest --testPathIgnorePatterns='resend|Resend' --testNamePattern='^((?!(resend|Resend|resent|Resent)).)*$' test/integration/*.test.js", + "test-integration-resend": "jest --testTimeout=15000 --testNamePattern='(resend|Resend|resent|Resent)' test/integration/*.test.js", + "test-integration-dataunions": "jest --testTimeout=15000 test/integration/DataUnionEndpoints", "test-flakey": "jest --forceExit test/flakey/*", "test-browser": "node ./test/browser/server.js & node node_modules/nightwatch/bin/nightwatch ./test/browser/browser.js && pkill -f server.js", "install-example": "cd examples/webpack && npm ci", From 0a64db0154e9072f5aac37b3b65affc5c0580837 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 7 Jan 2021 08:50:20 -0500 Subject: [PATCH 360/517] Reduce line length. --- src/rest/DataUnionEndpoints.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/rest/DataUnionEndpoints.js b/src/rest/DataUnionEndpoints.js index eda679422..a6f038e9e 100644 --- a/src/rest/DataUnionEndpoints.js +++ b/src/rest/DataUnionEndpoints.js @@ -443,7 +443,10 @@ async function untilWithdrawIsComplete(client, getWithdrawTxFunc, getBalanceFunc const failAddress = await mainnetAmb.failedMessageSender(messageId) if (alreadySent || failAddress !== '0x0000000000000000000000000000000000000000') { // zero address means no failed messages log(`WARNING: Mainnet bridge has already processed withdraw messageId=${messageId}`) - log('This could happen if payForSignatureTransport=true, but bridge operator also pays for signatures, and got there before your client') + log([ + 'This could happen if payForSignatureTransport=true, but bridge operator also pays for', + 'signatures, and got there before your client', + ].join(' ')) continue } From ab6935c6f342783927718479ea362bfd385b7e70 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 7 Jan 2021 10:31:10 -0500 Subject: [PATCH 361/517] Remove need for ethers in dependencies. --- src/StreamrClient.js | 4 +--- test/benchmarks/publish.js | 10 ++++++---- test/benchmarks/subscribe.js | 10 ++++++---- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 080ba43c5..1e23b6ec4 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -1,6 +1,6 @@ import EventEmitter from 'eventemitter3' import { Wallet } from '@ethersproject/wallet' -import { getDefaultProvider, providers } from 'ethers' +import { getDefaultProvider, JsonRpcProvider } from '@ethersproject/providers' import { ControlLayer } from 'streamr-client-protocol' import Debug from 'debug' @@ -13,8 +13,6 @@ import Publisher from './publish' import Subscriber from './subscribe' import { getUserId } from './user' -const { JsonRpcProvider } = providers - /** * Wrap connection message events with message parsing. */ diff --git a/test/benchmarks/publish.js b/test/benchmarks/publish.js index b731c7632..89cb1641e 100644 --- a/test/benchmarks/publish.js +++ b/test/benchmarks/publish.js @@ -1,7 +1,6 @@ const { format } = require('util') const { Benchmark } = require('benchmark') -const { ethers } = require('ethers') // eslint-disable-next-line import/no-unresolved const StreamrClient = require('../../dist/streamr-client.nodejs.js') @@ -46,23 +45,26 @@ const BATCH_SIZES = [ const log = (...args) => process.stderr.write(format(...args) + '\n') async function run() { + const account1 = StreamrClient.generateEthereumAccount() const [client1, stream1] = await setupClientAndStream({ auth: { - privateKey: ethers.Wallet.createRandom().privateKey, + privateKey: account1.privateKey, }, publishWithSignature: 'always', }) + const account2 = StreamrClient.generateEthereumAccount() const [client2, stream2] = await setupClientAndStream({ auth: { - privateKey: ethers.Wallet.createRandom().privateKey, + privateKey: account2.privateKey, }, publishWithSignature: 'never', }) + const account3 = StreamrClient.generateEthereumAccount() const [client3, stream3] = await setupClientAndStream({ auth: { - privateKey: ethers.Wallet.createRandom().privateKey, + privateKey: account3.privateKey, }, publishWithSignature: 'always', }, { diff --git a/test/benchmarks/subscribe.js b/test/benchmarks/subscribe.js index 504586774..07683e80b 100644 --- a/test/benchmarks/subscribe.js +++ b/test/benchmarks/subscribe.js @@ -1,7 +1,6 @@ const { format } = require('util') const { Benchmark } = require('benchmark') -const { ethers } = require('ethers') // eslint-disable-next-line import/no-unresolved const StreamrClient = require('../../dist/streamr-client.nodejs.js') @@ -46,23 +45,26 @@ const BATCH_SIZES = [ const log = (...args) => process.stderr.write(format(...args) + '\n') async function run() { + const account1 = StreamrClient.generateEthereumAccount() const [client1, stream1] = await setupClientAndStream({ auth: { - privateKey: ethers.Wallet.createRandom().privateKey, + privateKey: account1.privateKey, }, publishWithSignature: 'always', }) + const account2 = StreamrClient.generateEthereumAccount() const [client2, stream2] = await setupClientAndStream({ auth: { - privateKey: ethers.Wallet.createRandom().privateKey, + privateKey: account2.privateKey, }, publishWithSignature: 'never', }) + const account3 = StreamrClient.generateEthereumAccount() const [client3, stream3] = await setupClientAndStream({ auth: { - privateKey: ethers.Wallet.createRandom().privateKey, + privateKey: account3.privateKey, }, publishWithSignature: 'always', }, { From d71ae2f6662032670c3904a8734af8c0fd4c05a9 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 7 Jan 2021 10:31:36 -0500 Subject: [PATCH 362/517] Move ethers to devDependencies. Refresh package-lock. --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9a1991b8c..98c1b9746 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3874,9 +3874,9 @@ "dev": true }, "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.0.tgz", + "integrity": "sha512-JgQM9JS92ZbFR4P90EvmzNpSGhpPBGBSj10PILeDyYFwp4h2/D9OM03wsJ4zW1fEp4ka2DGrnUeD7FuvQ2aZ2Q==", "dev": true, "optional": true, "requires": { diff --git a/package.json b/package.json index 56610d0ad..f17aef47c 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ "eslint-loader": "^4.0.2", "eslint-plugin-import": "^2.22.1", "eslint-plugin-promise": "^4.2.1", + "ethers": "^5.0.21", "express": "^4.17.1", "geckodriver": "^1.21.0", "git-revision-webpack-plugin": "^3.0.6", @@ -90,7 +91,6 @@ "@ethersproject/transactions": "^5.0.4", "@ethersproject/wallet": "^5.0.6", "debug": "^4.3.1", - "ethers": "^5.0.21", "eventemitter3": "^4.0.7", "lodash.uniqueid": "^4.0.1", "mem": "^8.0.0", From 5052b72eae2268742f5345eb4523229ee9e8c03f Mon Sep 17 00:00:00 2001 From: Juuso Takalainen Date: Tue, 12 Jan 2021 14:40:25 +0200 Subject: [PATCH 363/517] bridge_collected is not part of (default) config anymore --- .github/workflows/nodejs.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index ae703beaa..5d87171f1 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -92,7 +92,7 @@ jobs: run: | sudo service mysql stop sudo ifconfig docker0 10.200.10.1/24 - ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server --except bridge_collected + ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server - name: Run Test run: npm run $TEST_NAME -- --maxWorkers=1 @@ -119,7 +119,7 @@ jobs: run: | sudo service mysql stop sudo ifconfig docker0 10.200.10.1/24 - ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server --except bridge_collected + ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server - uses: nick-invision/retry@v2 name: Run Test with: @@ -151,7 +151,7 @@ jobs: run: | sudo service mysql stop sudo ifconfig docker0 10.200.10.1/24 - ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server --except bridge_collected + ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server - name: test-browser timeout-minutes: 2 run: npm run test-browser @@ -181,7 +181,7 @@ jobs: run: | sudo service mysql stop sudo ifconfig docker0 10.200.10.1/24 - ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server --except bridge_collected + ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server - name: npm ci run: npm ci - name: benchmarks From ce61d80c43e0dabdd7af19db932016d2488a2260 Mon Sep 17 00:00:00 2001 From: Juuso Takalainen Date: Wed, 13 Jan 2021 13:53:16 +0200 Subject: [PATCH 364/517] re-add getAddress, getSigner, getSidechainSigner to StreamrClient these are needed in DataUnionEndpoints, but they certainly need a better place in code... --- src/StreamrClient.js | 49 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 1e23b6ec4..db0816793 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -1,6 +1,7 @@ import EventEmitter from 'eventemitter3' import { Wallet } from '@ethersproject/wallet' -import { getDefaultProvider, JsonRpcProvider } from '@ethersproject/providers' +import { getDefaultProvider, JsonRpcProvider, Web3Provider } from '@ethersproject/providers' +import { computeAddress } from '@ethersproject/transactions' import { ControlLayer } from 'streamr-client-protocol' import Debug from 'debug' @@ -137,6 +138,52 @@ export default class StreamrClient extends EventEmitter { this.publisher = new Publisher(this) this.subscriber = new Subscriber(this) this.cached = new StreamrCached(this) + + // TODO: below could also be written "OOP style" into two classes implementing an "interface StreamrEthereum" if porting to TypeScript later + // typically: in node.js, privateKey is used; in browser, (window.)ethereum is used + if (this.options.auth.privateKey) { + if (!this.options.auth.privateKey.startsWith('0x')) { + this.options.auth.privateKey = `0x${this.options.auth.privateKey}` + } + const self = this + const key = this.options.auth.privateKey + const address = computeAddress(key) + this.getAddress = () => address + this.getSigner = () => new Wallet(key, self.getMainnetProvider()) + this.getSidechainSigner = async () => new Wallet(key, self.getSidechainProvider()) + } else if (this.options.auth.ethereum) { + const self = this + this.getAddress = () => self.options.auth.ethereum.selectedAddress // null if no addresses connected+selected in Metamask + this.getSigner = () => { + const metamaskProvider = new Web3Provider(self.options.auth.ethereum) + const metamaskSigner = metamaskProvider.getSigner() + return metamaskSigner + } + this.getSidechainSigner = async () => { + // chainId is required for checking when using Metamask + if (!self.options.sidechain || !self.options.sidechain.chainId) { + throw new Error('Streamr sidechain not configured (with chainId) in the StreamrClient options!') + } + + const metamaskProvider = new Web3Provider(self.options.auth.ethereum) + const { chainId } = await metamaskProvider.getNetwork() + if (chainId !== self.options.sidechain.chainId) { + throw new Error(`Please connect Metamask to Ethereum blockchain with chainId ${self.options.sidechain.chainId}`) + } + const metamaskSigner = metamaskProvider.getSigner() + return metamaskSigner + } + // TODO: handle events + // ethereum.on('accountsChanged', (accounts) => { }) + // https://docs.metamask.io/guide/ethereum-provider.html#events says: + // "We recommend reloading the page unless you have a very good reason not to" + // Of course we can't and won't do that, but if we need something chain-dependent... + // ethereum.on('chainChanged', (chainId) => { window.location.reload() }); + } else { + this.getAddress = () => null + this.getSigner = () => { throw new Error("StreamrClient not authenticated! Can't send transactions or sign messages.") } + this.getSidechainSigner = async () => { throw new Error("StreamrClient not authenticated! Can't send transactions or sign messages.") } + } } async onConnectionConnected() { From 90f07544ef769408d1784fb8ed7b390a406a3524 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 15 Jan 2021 14:03:33 -0500 Subject: [PATCH 365/517] Release v5.0.0-beta.2 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 98c1b9746..4dc973ab7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.1", + "version": "5.0.0-beta.2", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index f17aef47c..e5394d7c0 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.1", + "version": "5.0.0-beta.2", "description": "JavaScript client library for Streamr", "repository": { "type": "git", From 97e216343972158b4a583e603bcdf9b55a13914a Mon Sep 17 00:00:00 2001 From: Juuso Takalainen Date: Thu, 14 Jan 2021 16:12:34 +0200 Subject: [PATCH 366/517] get token address from a data union if available if token address hasn't been explicitly given --- src/rest/DataUnionEndpoints.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rest/DataUnionEndpoints.js b/src/rest/DataUnionEndpoints.js index a6f038e9e..8354fe600 100644 --- a/src/rest/DataUnionEndpoints.js +++ b/src/rest/DataUnionEndpoints.js @@ -929,7 +929,7 @@ export async function getMemberBalance(memberAddress, options) { export async function getTokenBalance(address, options) { const a = parseAddress(this, address, options) - const tokenAddressMainnet = this.options.tokenAddress || options.tokenAddress + const tokenAddressMainnet = this.options.tokenAddress || options.tokenAddress || (await getMainnetContractReadOnly(this, options)).token() if (!tokenAddressMainnet) { throw new Error('tokenAddress option not found') } const provider = this.getMainnetProvider() const token = new Contract(tokenAddressMainnet, [{ From 5e0cf02e1ab97d4b530366a66bc96bc419a49d47 Mon Sep 17 00:00:00 2001 From: Juuso Takalainen Date: Thu, 14 Jan 2021 16:12:48 +0200 Subject: [PATCH 367/517] fix parseAddress argument count --- src/rest/DataUnionEndpoints.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/rest/DataUnionEndpoints.js b/src/rest/DataUnionEndpoints.js index 8354fe600..7c45c09ac 100644 --- a/src/rest/DataUnionEndpoints.js +++ b/src/rest/DataUnionEndpoints.js @@ -826,7 +826,7 @@ export async function joinDataUnion(options = {}) { const dataUnion = getMainnetContractReadOnly(this, options) const body = { - memberAddress: parseAddress(this, member, options) + memberAddress: parseAddress(this, member) } if (secret) { body.secret = secret } @@ -856,7 +856,7 @@ export async function hasJoined(memberAddress, options = {}) { pollingIntervalMs = 1000, retryTimeoutMs = 60000, } = options - const address = parseAddress(this, memberAddress, options) + const address = parseAddress(this, memberAddress) const duSidechain = await getSidechainContractReadOnly(this, options) // memberData[0] is enum ActiveStatus {None, Active, Inactive}, and zero means member has never joined @@ -898,7 +898,7 @@ export async function getDataUnionStats(options) { * @param {EthereumAddress} memberAddress (optional) if not supplied, get the stats of currently logged in StreamrClient (if auth: privateKey) */ export async function getMemberStats(memberAddress, options) { - const address = parseAddress(this, memberAddress, options) + const address = parseAddress(this, memberAddress) // TODO: use duSidechain.getMemberStats(address) once it's implemented, to ensure atomic read // (so that memberData is from same block as getEarnings, otherwise withdrawable will be foobar) const duSidechain = await getSidechainContractReadOnly(this, options) @@ -922,13 +922,13 @@ export async function getMemberStats(memberAddress, options) { * @return {Promise} */ export async function getMemberBalance(memberAddress, options) { - const address = parseAddress(this, memberAddress, options) + const address = parseAddress(this, memberAddress) const duSidechain = await getSidechainContractReadOnly(this, options) return duSidechain.getWithdrawableEarnings(address) } export async function getTokenBalance(address, options) { - const a = parseAddress(this, address, options) + const a = parseAddress(this, address) const tokenAddressMainnet = this.options.tokenAddress || options.tokenAddress || (await getMainnetContractReadOnly(this, options)).token() if (!tokenAddressMainnet) { throw new Error('tokenAddress option not found') } const provider = this.getMainnetProvider() From 65bd8b7c95575245888ce51095c37d4353e3393e Mon Sep 17 00:00:00 2001 From: Juuso Takalainen Date: Thu, 14 Jan 2021 16:21:45 +0200 Subject: [PATCH 368/517] More "reasonable" resolution order for WHICH token balance the caller wants First see if there is a specified dataUnion. It should take priority unless function call itself specified which token to use, only then use tokenAddress from constructor (which probably just has the default value, (real) mainnet DATA token address (which breaks in dev env) --- src/rest/DataUnionEndpoints.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/rest/DataUnionEndpoints.js b/src/rest/DataUnionEndpoints.js index 7c45c09ac..4bea609ef 100644 --- a/src/rest/DataUnionEndpoints.js +++ b/src/rest/DataUnionEndpoints.js @@ -927,9 +927,15 @@ export async function getMemberBalance(memberAddress, options) { return duSidechain.getWithdrawableEarnings(address) } +/** + * Get token balance for given address + * @param {EthereumAddress} address + * @param options such as tokenAddress. If not given, then first check if dataUnion was given in StreamrClient constructor, then check if tokenAddress was given in StreamrClient constructor. + * @returns {Promise} token balance in "wei" (10^-18 parts) + */ export async function getTokenBalance(address, options) { const a = parseAddress(this, address) - const tokenAddressMainnet = this.options.tokenAddress || options.tokenAddress || (await getMainnetContractReadOnly(this, options)).token() + const tokenAddressMainnet = options.tokenAddress || await getMainnetContractReadOnly(this, options).then(c => c.token()).catch(e => null) || this.options.tokenAddress if (!tokenAddressMainnet) { throw new Error('tokenAddress option not found') } const provider = this.getMainnetProvider() const token = new Contract(tokenAddressMainnet, [{ From 0b8a97392fb2931f2e59183c8b4f0b5cf353403e Mon Sep 17 00:00:00 2001 From: Juuso Takalainen Date: Thu, 14 Jan 2021 16:22:40 +0200 Subject: [PATCH 369/517] fix lint --- src/rest/DataUnionEndpoints.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rest/DataUnionEndpoints.js b/src/rest/DataUnionEndpoints.js index 4bea609ef..9b8053ce4 100644 --- a/src/rest/DataUnionEndpoints.js +++ b/src/rest/DataUnionEndpoints.js @@ -935,7 +935,7 @@ export async function getMemberBalance(memberAddress, options) { */ export async function getTokenBalance(address, options) { const a = parseAddress(this, address) - const tokenAddressMainnet = options.tokenAddress || await getMainnetContractReadOnly(this, options).then(c => c.token()).catch(e => null) || this.options.tokenAddress + const tokenAddressMainnet = options.tokenAddress || await getMainnetContractReadOnly(this, options).then((c) => c.token()).catch((e) => null) || this.options.tokenAddress if (!tokenAddressMainnet) { throw new Error('tokenAddress option not found') } const provider = this.getMainnetProvider() const token = new Contract(tokenAddressMainnet, [{ From 8c9c4af7b6dfced464e84d04520d91a004e4cf92 Mon Sep 17 00:00:00 2001 From: Juuso Takalainen Date: Thu, 14 Jan 2021 16:23:54 +0200 Subject: [PATCH 370/517] minor cleanup --- src/rest/DataUnionEndpoints.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/rest/DataUnionEndpoints.js b/src/rest/DataUnionEndpoints.js index 9b8053ce4..1a19e2a8e 100644 --- a/src/rest/DataUnionEndpoints.js +++ b/src/rest/DataUnionEndpoints.js @@ -935,7 +935,7 @@ export async function getMemberBalance(memberAddress, options) { */ export async function getTokenBalance(address, options) { const a = parseAddress(this, address) - const tokenAddressMainnet = options.tokenAddress || await getMainnetContractReadOnly(this, options).then((c) => c.token()).catch((e) => null) || this.options.tokenAddress + const tokenAddressMainnet = options.tokenAddress || await getMainnetContractReadOnly(this, options).then((c) => c.token()).catch(() => null) || this.options.tokenAddress if (!tokenAddressMainnet) { throw new Error('tokenAddress option not found') } const provider = this.getMainnetProvider() const token = new Contract(tokenAddressMainnet, [{ From ff0c8fcea936aa3b5fc3ac5cab129d7ab3e2af56 Mon Sep 17 00:00:00 2001 From: jtakalai Date: Thu, 14 Jan 2021 20:01:44 +0200 Subject: [PATCH 371/517] Merge pull request #189 from streamr-dev/fix-token-balance-getter Small fixes to DU-2 From ce1a8f472be64340745efa720812a815229759df Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 19 Jan 2021 14:26:19 -0500 Subject: [PATCH 372/517] Break Ethereum methods out of StreamrClient & into new file src/Ethereum. --- src/Ethereum.js | 88 ++++++++++++++++++++++++++++++++++++++++ src/StreamrClient.js | 96 ++++++++++++-------------------------------- 2 files changed, 114 insertions(+), 70 deletions(-) create mode 100644 src/Ethereum.js diff --git a/src/Ethereum.js b/src/Ethereum.js new file mode 100644 index 000000000..19bf0bfaf --- /dev/null +++ b/src/Ethereum.js @@ -0,0 +1,88 @@ +import { Wallet } from '@ethersproject/wallet' +import { getDefaultProvider, JsonRpcProvider, Web3Provider } from '@ethersproject/providers' +import { computeAddress } from '@ethersproject/transactions' + +export default class StreamrEthereum { + static generateEthereumAccount() { + const wallet = Wallet.createRandom() + return { + address: wallet.address, + privateKey: wallet.privateKey, + } + } + + constructor(client) { + this.client = client + const { options } = client + const { auth } = options + if (auth.privateKey) { + const key = auth.privateKey + const address = computeAddress(key) + this.getAddress = () => address + this.getSigner = () => new Wallet(key, this.getMainnetProvider()) + this.getSidechainSigner = async () => new Wallet(key, this.getSidechainProvider()) + } else if (auth.ethereum) { + this.getAddress = () => auth.ethereum.selectedAddress // null if no addresses connected+selected in Metamask + this.getSigner = () => { + const metamaskProvider = new Web3Provider(auth.ethereum) + const metamaskSigner = metamaskProvider.getSigner() + return metamaskSigner + } + this.getSidechainSigner = async () => { + // chainId is required for checking when using Metamask + if (!options.sidechain || !options.sidechain.chainId) { + throw new Error('Streamr sidechain not configured (with chainId) in the StreamrClient options!') + } + + const metamaskProvider = new Web3Provider(auth.ethereum) + const { chainId } = await metamaskProvider.getNetwork() + if (chainId !== options.sidechain.chainId) { + throw new Error(`Please connect Metamask to Ethereum blockchain with chainId ${options.sidechain.chainId}`) + } + const metamaskSigner = metamaskProvider.getSigner() + return metamaskSigner + } + // TODO: handle events + // ethereum.on('accountsChanged', (accounts) => { }) + // https://docs.metamask.io/guide/ethereum-provider.html#events says: + // "We recommend reloading the page unless you have a very good reason not to" + // Of course we can't and won't do that, but if we need something chain-dependent... + // ethereum.on('chainChanged', (chainId) => { window.location.reload() }); + } + } + + /* eslint-disable class-methods-use-this */ + + getAddress() { + // default. should be overridden in constructor based on options + return null + } + + getSigner() { + // default. should be overridden in constructor based on options + throw new Error("StreamrClient not authenticated! Can't send transactions or sign messages.") + } + + async getSidechainSigner() { + // default. should be overridden in constructor based on options + throw new Error("StreamrClient not authenticated! Can't send transactions or sign messages.") + } + + /* eslint-enable class-methods-use-this */ + + /** @returns Ethers.js Provider, a connection to the Ethereum network (mainnet) */ + getMainnetProvider() { + if (this.client.options.mainnet) { + return new JsonRpcProvider(this.client.options.mainnet) + } + return getDefaultProvider() + } + + /** @returns Ethers.js Provider, a connection to the Streamr EVM sidechain */ + getSidechainProvider() { + if (this.client.options.sidechain) { + return new JsonRpcProvider(this.client.options.sidechain) + } + return null + } +} diff --git a/src/StreamrClient.js b/src/StreamrClient.js index db0816793..26e9c2810 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -1,13 +1,11 @@ import EventEmitter from 'eventemitter3' -import { Wallet } from '@ethersproject/wallet' -import { getDefaultProvider, JsonRpcProvider, Web3Provider } from '@ethersproject/providers' -import { computeAddress } from '@ethersproject/transactions' import { ControlLayer } from 'streamr-client-protocol' import Debug from 'debug' import { counterId, uuid, CacheAsyncFn } from './utils' import { validateOptions } from './stream/utils' import Config from './Config' +import StreamrEthereum from './Ethereum' import Session from './Session' import Connection from './Connection' import Publisher from './publish' @@ -138,52 +136,7 @@ export default class StreamrClient extends EventEmitter { this.publisher = new Publisher(this) this.subscriber = new Subscriber(this) this.cached = new StreamrCached(this) - - // TODO: below could also be written "OOP style" into two classes implementing an "interface StreamrEthereum" if porting to TypeScript later - // typically: in node.js, privateKey is used; in browser, (window.)ethereum is used - if (this.options.auth.privateKey) { - if (!this.options.auth.privateKey.startsWith('0x')) { - this.options.auth.privateKey = `0x${this.options.auth.privateKey}` - } - const self = this - const key = this.options.auth.privateKey - const address = computeAddress(key) - this.getAddress = () => address - this.getSigner = () => new Wallet(key, self.getMainnetProvider()) - this.getSidechainSigner = async () => new Wallet(key, self.getSidechainProvider()) - } else if (this.options.auth.ethereum) { - const self = this - this.getAddress = () => self.options.auth.ethereum.selectedAddress // null if no addresses connected+selected in Metamask - this.getSigner = () => { - const metamaskProvider = new Web3Provider(self.options.auth.ethereum) - const metamaskSigner = metamaskProvider.getSigner() - return metamaskSigner - } - this.getSidechainSigner = async () => { - // chainId is required for checking when using Metamask - if (!self.options.sidechain || !self.options.sidechain.chainId) { - throw new Error('Streamr sidechain not configured (with chainId) in the StreamrClient options!') - } - - const metamaskProvider = new Web3Provider(self.options.auth.ethereum) - const { chainId } = await metamaskProvider.getNetwork() - if (chainId !== self.options.sidechain.chainId) { - throw new Error(`Please connect Metamask to Ethereum blockchain with chainId ${self.options.sidechain.chainId}`) - } - const metamaskSigner = metamaskProvider.getSigner() - return metamaskSigner - } - // TODO: handle events - // ethereum.on('accountsChanged', (accounts) => { }) - // https://docs.metamask.io/guide/ethereum-provider.html#events says: - // "We recommend reloading the page unless you have a very good reason not to" - // Of course we can't and won't do that, but if we need something chain-dependent... - // ethereum.on('chainChanged', (chainId) => { window.location.reload() }); - } else { - this.getAddress = () => null - this.getSigner = () => { throw new Error("StreamrClient not authenticated! Can't send transactions or sign messages.") } - this.getSidechainSigner = async () => { throw new Error("StreamrClient not authenticated! Can't send transactions or sign messages.") } - } + this.ethereum = new StreamrEthereum(this) } async onConnectionConnected() { @@ -219,25 +172,10 @@ export default class StreamrClient extends EventEmitter { return this.connection.send(request) } - /** @returns Ethers.js Provider, a connection to the Ethereum network (mainnet) */ - getMainnetProvider() { - if (this.options.mainnet) { - return new JsonRpcProvider(this.options.mainnet) - } - return getDefaultProvider() - } - - /** @returns Ethers.js Provider, a connection to the Streamr EVM sidechain */ - getSidechainProvider() { - if (this.options.sidechain) { - return new JsonRpcProvider(this.options.sidechain) - } - return null - } - /** * Override to control output */ + onError(error) { // eslint-disable-line class-methods-use-this console.error(error) } @@ -372,11 +310,29 @@ export default class StreamrClient extends EventEmitter { return this.connection.enableAutoDisconnect(...args) } + getAddress() { + return this.ethereum.getAddress() + } + + getSigner() { + return this.ethereum.getSigner() + } + + async getSidechainSigner() { + return this.ethereum.getSidechainSigner() + } + + /** @returns Ethers.js Provider, a connection to the Ethereum network (mainnet) */ + getMainnetProvider() { + return this.ethereum.getMainnetProvider() + } + + /** @returns Ethers.js Provider, a connection to the Streamr EVM sidechain */ + getSidechainProvider() { + return this.ethereum.getSidechainProvider() + } + static generateEthereumAccount() { - const wallet = Wallet.createRandom() - return { - address: wallet.address, - privateKey: wallet.privateKey, - } + return StreamrEthereum.generateEthereumAccount() } } From abd0e7f6e97397d27e70148eb9eeed317325be79 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 19 Jan 2021 14:50:17 -0500 Subject: [PATCH 373/517] Fix client version & git status logging on startup to work for node + ci + jest. --- jest.config.js | 4 ++-- jest.setup.js | 13 +++++++++++++ src/StreamrClient.js | 1 + webpack.config.js | 7 ++++--- 4 files changed, 20 insertions(+), 5 deletions(-) diff --git a/jest.config.js b/jest.config.js index 5ff6d027a..195c4cda2 100644 --- a/jest.config.js +++ b/jest.config.js @@ -51,7 +51,7 @@ module.exports = { // forceCoverageMatch: [], // A path to a module which exports an async function that is triggered once before all test suites - // globalSetup: null, + globalSetup: './jest.setup.js', // A path to a module which exports an async function that is triggered once after all test suites // globalTeardown: null, @@ -121,7 +121,7 @@ module.exports = { // The path to a module that runs some code to configure or set up the testing framework before each test // setupTestFrameworkScriptFile: null, - setupFilesAfterEnv: ['./jest.setup.js'], + // setupFilesAfterEnv: [], // A list of paths to snapshot serializer modules Jest should use for snapshot testing // snapshotSerializers: [], diff --git a/jest.setup.js b/jest.setup.js index 2482d0e8e..37a553774 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,4 +1,5 @@ const Debug = require('debug') +const GitRevisionPlugin = require('git-revision-webpack-plugin') if (process.env.DEBUG_CONSOLE) { // Use debug as console log @@ -6,3 +7,15 @@ if (process.env.DEBUG_CONSOLE) { // Ensuring debug messages are printed alongside console messages, in the correct order console.log = Debug('Streamr::CONSOLE') // eslint-disable-line no-console } + +console.log(process.env) +module.exports = async () => { + if (!process.env.GIT_VERSION) { + const gitRevisionPlugin = new GitRevisionPlugin() + Object.assign(process.env, { + GIT_VERSION: await gitRevisionPlugin.version(), + GIT_COMMITHASH: await gitRevisionPlugin.commithash(), + GIT_BRANCH: await gitRevisionPlugin.branch(), + }, process.env) + } +} diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 5b0f75377..5e684f3c3 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -103,6 +103,7 @@ export default class StreamrClient extends EventEmitter { }) this.debug('new StreamrClient %s: %o', this.id, { + version: process.env.version, GIT_VERSION: process.env.GIT_VERSION, GIT_COMMITHASH: process.env.GIT_COMMITHASH, GIT_BRANCH: process.env.GIT_BRANCH, diff --git a/webpack.config.js b/webpack.config.js index 81b790f7d..5c8c198d7 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -64,11 +64,12 @@ module.exports = (env, argv) => { extensions: ['.json', '.js'], }, plugins: [ - new webpack.DefinePlugin({ - 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV), + new webpack.EnvironmentPlugin({ + NODE_ENV: JSON.stringify(process.env.NODE_ENV), }), gitRevisionPlugin, - new webpack.DefinePlugin({ + new webpack.EnvironmentPlugin({ + version: JSON.stringify(pkg.version), GIT_VERSION: JSON.stringify(gitRevisionPlugin.version()), GIT_COMMITHASH: JSON.stringify(gitRevisionPlugin.commithash()), GIT_BRANCH: JSON.stringify(gitRevisionPlugin.branch()), From 987e53d4887990a825a901cf8719a68af0972473 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 19 Jan 2021 14:55:09 -0500 Subject: [PATCH 374/517] Fetch git info for test env in parallel. --- jest.setup.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/jest.setup.js b/jest.setup.js index 37a553774..62b35bddd 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -8,14 +8,18 @@ if (process.env.DEBUG_CONSOLE) { console.log = Debug('Streamr::CONSOLE') // eslint-disable-line no-console } -console.log(process.env) module.exports = async () => { if (!process.env.GIT_VERSION) { const gitRevisionPlugin = new GitRevisionPlugin() + const [GIT_VERSION, GIT_COMMITHASH, GIT_BRANCH] = await Promise.all([ + gitRevisionPlugin.version(), + gitRevisionPlugin.commithash(), + gitRevisionPlugin.branch(), + ]) Object.assign(process.env, { - GIT_VERSION: await gitRevisionPlugin.version(), - GIT_COMMITHASH: await gitRevisionPlugin.commithash(), - GIT_BRANCH: await gitRevisionPlugin.branch(), - }, process.env) + GIT_VERSION, + GIT_COMMITHASH, + GIT_BRANCH, + }, process.env) // don't override whatever is in process.env } } From 1ecd3515edf2b0dcb6bab11c35a6272479836b23 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 19 Jan 2021 15:24:17 -0500 Subject: [PATCH 375/517] Add ws url config variations to CI integration test matrix. --- .github/workflows/nodejs.yml | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 5d87171f1..cfc652f32 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -67,15 +67,31 @@ jobs: run: npm run test-unit integration: - name: ${{ matrix.test-name }} using Node ${{ matrix.node-version }} + name: ${{ matrix.test-name }} ${{ websocket-url.name }} using Node ${{ matrix.node-version }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: node-version: [12.x, 14.x] - test-name: ["test-integration-no-resend", "test-integration-resend", "test-integration-dataunions"] + test-name: [ + "test-integration-no-resend", + "test-integration-resend", + "test-integration-dataunions", + ] + websocket-url: + - name: "default" + url: "" + - name: "storage-node-only" + url: "ws://localhost:8890/api/v1/ws" + + exclude: + # no need to test different ws urls for dataunion tests + - test-name: "test-integration-dataunions" + websocket-url: + - name: "storage-node-only" env: - TEST_NAME: ${{ matrix.test-name }} + TEST_NAME: ${{ matrix.test-name }} + WEBSOCKET_URL: ${{ matrix.websocket-url.url}} steps: - uses: actions/checkout@v2 From 44fffba61b0c1676bf9510c16006a7ca3db64df8 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 19 Jan 2021 15:34:03 -0500 Subject: [PATCH 376/517] Fix up CI yaml. --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index cfc652f32..e5746d87e 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -67,7 +67,7 @@ jobs: run: npm run test-unit integration: - name: ${{ matrix.test-name }} ${{ websocket-url.name }} using Node ${{ matrix.node-version }} + name: ${{ matrix.test-name }} ${{ matrix.websocket-url.name }} using Node ${{ matrix.node-version }} runs-on: ubuntu-latest strategy: fail-fast: false From 245a0dd7a038ea551cfd5617def0b6c3f53c021f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 19 Jan 2021 15:36:00 -0500 Subject: [PATCH 377/517] Try fix integration test matrix exclude. --- .github/workflows/nodejs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index e5746d87e..bdfee81da 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -89,6 +89,7 @@ jobs: - test-name: "test-integration-dataunions" websocket-url: - name: "storage-node-only" + - url: "ws://localhost:8890/api/v1/ws" env: TEST_NAME: ${{ matrix.test-name }} WEBSOCKET_URL: ${{ matrix.websocket-url.url}} From e0c4d47a6c1392be92bac350fec19dde2c2ec1b1 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 19 Jan 2021 15:48:06 -0500 Subject: [PATCH 378/517] Replace manual setup steps with streamr-docker-dev-action in CI. --- .github/workflows/nodejs.yml | 55 ++++++++++-------------------------- 1 file changed, 15 insertions(+), 40 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index bdfee81da..87ff30579 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -101,15 +101,10 @@ jobs: node-version: ${{ matrix.node-version }} - name: npm ci run: npm ci - - uses: actions/checkout@v2 + - name: Start Streamr Docker Stack + uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.2 with: - repository: streamr-dev/streamr-docker-dev - path: streamr-docker-dev - - name: setup streamr-docker-dev - run: | - sudo service mysql stop - sudo ifconfig docker0 10.200.10.1/24 - ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server + services-to-start: "mysql redis engine-and-editor cassandra parity-node0 parity-sidechain-node0 bridge broker-node-storage-1 nginx smtp" - name: Run Test run: npm run $TEST_NAME -- --maxWorkers=1 @@ -128,15 +123,10 @@ jobs: node-version: ${{ matrix.node-version }} - name: npm ci run: npm ci - - uses: actions/checkout@v2 + - name: Start Streamr Docker Stack + uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.2 with: - repository: streamr-dev/streamr-docker-dev - path: streamr-docker-dev - - name: setup streamr-docker-dev - run: | - sudo service mysql stop - sudo ifconfig docker0 10.200.10.1/24 - ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server + services-to-start: "mysql redis engine-and-editor cassandra parity-node0 parity-sidechain-node0 bridge broker-node-storage-1 nginx smtp" - uses: nick-invision/retry@v2 name: Run Test with: @@ -160,15 +150,10 @@ jobs: path: dist - name: npm ci run: npm ci - - uses: actions/checkout@v2 + - name: Start Streamr Docker Stack + uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.2 with: - repository: streamr-dev/streamr-docker-dev - path: streamr-docker-dev - - name: setup streamr-docker-dev - run: | - sudo service mysql stop - sudo ifconfig docker0 10.200.10.1/24 - ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server + services-to-start: "mysql redis engine-and-editor cassandra parity-node0 parity-sidechain-node0 bridge broker-node-storage-1 nginx smtp" - name: test-browser timeout-minutes: 2 run: npm run test-browser @@ -190,15 +175,10 @@ jobs: with: name: build path: dist - - uses: actions/checkout@v2 + - name: Start Streamr Docker Stack + uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.2 with: - repository: streamr-dev/streamr-docker-dev - path: streamr-docker-dev - - name: setup streamr-docker-dev - run: | - sudo service mysql stop - sudo ifconfig docker0 10.200.10.1/24 - ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start --wait --except data-union-server + services-to-start: "mysql redis engine-and-editor cassandra parity-node0 parity-sidechain-node0 bridge broker-node-storage-1 nginx smtp" - name: npm ci run: npm ci - name: benchmarks @@ -242,15 +222,10 @@ jobs: with: name: build path: dist - - uses: actions/checkout@v2 + - name: Start Streamr Docker Stack + uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.2 with: - repository: streamr-dev/streamr-docker-dev - path: streamr-docker-dev - - name: setup streamr-docker-dev - run: | - sudo service mysql stop - sudo ifconfig docker0 10.200.10.1/24 - ${GITHUB_WORKSPACE}/streamr-docker-dev/streamr-docker-dev/bin.sh start nginx broker-node-storage-1 broker-node-no-storage-1 broker-node-no-storage-2 engine-and-editor tracker-1 --wait + services-to-start: "mysql redis engine-and-editor cassandra parity-node0 parity-sidechain-node0 bridge broker-node-storage-1 nginx smtp" - name: npm link run: npm ci && npm link - uses: actions/checkout@v2 From 26444ff47f691d03a80bbf2bb4ced3b2ddf041b8 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 20 Jan 2021 15:13:12 -0500 Subject: [PATCH 379/517] Remove internal-use only Ethereum methods from client API. --- src/StreamrClient.js | 18 --------------- src/rest/DataUnionEndpoints.js | 40 +++++++++++++++++----------------- 2 files changed, 20 insertions(+), 38 deletions(-) diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 26e9c2810..8ad3b7a20 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -314,24 +314,6 @@ export default class StreamrClient extends EventEmitter { return this.ethereum.getAddress() } - getSigner() { - return this.ethereum.getSigner() - } - - async getSidechainSigner() { - return this.ethereum.getSidechainSigner() - } - - /** @returns Ethers.js Provider, a connection to the Ethereum network (mainnet) */ - getMainnetProvider() { - return this.ethereum.getMainnetProvider() - } - - /** @returns Ethers.js Provider, a connection to the Streamr EVM sidechain */ - getSidechainProvider() { - return this.ethereum.getSidechainProvider() - } - static generateEthereumAccount() { return StreamrEthereum.generateEthereumAccount() } diff --git a/src/rest/DataUnionEndpoints.js b/src/rest/DataUnionEndpoints.js index 1a19e2a8e..414a97f74 100644 --- a/src/rest/DataUnionEndpoints.js +++ b/src/rest/DataUnionEndpoints.js @@ -285,10 +285,10 @@ let cachedSidechainAmb async function getSidechainAmb(client, options) { if (!cachedSidechainAmb) { const getAmbPromise = async () => { - const mainnetProvider = client.getMainnetProvider() + const mainnetProvider = client.ethereum.getMainnetProvider() const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, mainnetProvider) - const sidechainProvider = client.getSidechainProvider() + const sidechainProvider = client.ethereum.getSidechainProvider() const factorySidechainAddress = await factoryMainnet.data_union_sidechain_factory() const factorySidechain = new Contract(factorySidechainAddress, [{ name: 'amb', @@ -307,7 +307,7 @@ async function getSidechainAmb(client, options) { } async function getMainnetAmb(client, options) { - const mainnetProvider = client.getMainnetProvider() + const mainnetProvider = client.ethereum.getMainnetProvider() const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, mainnetProvider) const mainnetAmbAddress = await factoryMainnet.amb() @@ -368,7 +368,7 @@ async function transportSignatures(client, messageHash, options) { } log('Gas estimation failed: Check if number of signatures is enough') - const mainnetProvider = client.getMainnetProvider() + const mainnetProvider = client.ethereum.getMainnetProvider() const validatorContractAddress = await mainnetAmb.validatorContract() const validatorContract = new Contract(validatorContractAddress, [{ name: 'isValidator', @@ -403,7 +403,7 @@ async function transportSignatures(client, messageHash, options) { throw new Error(`Gas estimation failed: Unknown error while processing message ${message} with ${e.stack}`) } - const signer = client.getSigner() + const signer = client.ethereum.getSigner() log(`Sending message from signer=${await signer.getAddress()}`) const txAMB = await mainnetAmb.connect(signer).executeSignatures(message, packedSignatures) const trAMB = await txAMB.wait() @@ -468,7 +468,7 @@ const mainnetAddressCache = {} // mapping: "name" -> mainnet address /** @returns {Promise} Mainnet address for Data Union */ async function getDataUnionMainnetAddress(client, dataUnionName, deployerAddress, options = {}) { if (!mainnetAddressCache[dataUnionName]) { - const provider = client.getMainnetProvider() + const provider = client.ethereum.getMainnetProvider() const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, provider) const addressPromise = factoryMainnet.mainnetAddress(deployerAddress, dataUnionName) @@ -483,7 +483,7 @@ const sidechainAddressCache = {} // mapping: mainnet address -> sidechain addres /** @returns {Promise} Sidechain address for Data Union */ async function getDataUnionSidechainAddress(client, duMainnetAddress, options = {}) { if (!sidechainAddressCache[duMainnetAddress]) { - const provider = client.getMainnetProvider() + const provider = client.ethereum.getMainnetProvider() const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, provider) const addressPromise = factoryMainnet.sidechainAddress(duMainnetAddress) @@ -496,7 +496,7 @@ async function getDataUnionSidechainAddress(client, duMainnetAddress, options = function getMainnetContractReadOnly(client, options = {}) { let dataUnion = options.dataUnion || options.dataUnionAddress || client.options.dataUnion if (isAddress(dataUnion)) { - const provider = client.getMainnetProvider() + const provider = client.ethereum.getMainnetProvider() dataUnion = new Contract(dataUnion, dataUnionMainnetABI, provider) } @@ -508,12 +508,12 @@ function getMainnetContractReadOnly(client, options = {}) { function getMainnetContract(client, options = {}) { const du = getMainnetContractReadOnly(client, options) - const signer = client.getSigner() + const signer = client.ethereum.getSigner() return du.connect(signer) } async function getSidechainContract(client, options = {}) { - const signer = await client.getSidechainSigner() + const signer = await client.ethereum.getSidechainSigner() const duMainnet = getMainnetContractReadOnly(client, options) const duSidechainAddress = await getDataUnionSidechainAddress(client, duMainnet.address, options) const duSidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, signer) @@ -521,7 +521,7 @@ async function getSidechainContract(client, options = {}) { } async function getSidechainContractReadOnly(client, options = {}) { - const provider = await client.getSidechainProvider() + const provider = await client.ethereum.getSidechainProvider() const duMainnet = getMainnetContractReadOnly(client, options) const duSidechainAddress = await getDataUnionSidechainAddress(client, duMainnet.address, options) const duSidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, provider) @@ -591,12 +591,12 @@ export async function deployDataUnion(options = {}) { if (adminFee < 0 || adminFee > 1) { throw new Error('options.adminFeeFraction must be a number between 0...1, got: ' + adminFee) } const adminFeeBN = BigNumber.from((adminFee * 1e18).toFixed()) // last 2...3 decimals are going to be gibberish - const mainnetProvider = this.getMainnetProvider() - const mainnetWallet = this.getSigner() - const sidechainProvider = this.getSidechainProvider() + const mainnetProvider = this.ethererum.getMainnetProvider() + const mainnetWallet = this.ethererum.getSigner() + const sidechainProvider = this.ethererum.getSidechainProvider() // parseAddress defaults to authenticated user (also if "owner" is not an address) - const ownerAddress = await parseAddress(this, owner) + const ownerAddress = parseAddress(this, owner) let agentAddressList if (Array.isArray(joinPartAgents)) { @@ -937,7 +937,7 @@ export async function getTokenBalance(address, options) { const a = parseAddress(this, address) const tokenAddressMainnet = options.tokenAddress || await getMainnetContractReadOnly(this, options).then((c) => c.token()).catch(() => null) || this.options.tokenAddress if (!tokenAddressMainnet) { throw new Error('tokenAddress option not found') } - const provider = this.getMainnetProvider() + const provider = this.ethereum.getMainnetProvider() const token = new Contract(tokenAddressMainnet, [{ name: 'balanceOf', inputs: [{ type: 'address' }], @@ -958,7 +958,7 @@ export async function getTokenBalance(address, options) { */ export async function getDataUnionVersion(contractAddress) { const a = getAddress(contractAddress) // throws if bad address - const provider = this.getMainnetProvider() + const provider = this.ethereum.getMainnetProvider() const du = new Contract(a, [{ name: 'version', inputs: [], @@ -999,7 +999,7 @@ export async function withdraw(options = {}) { * @returns {Promise} await on call .wait to actually send the tx */ export async function getWithdrawTx(options) { - const signer = await this.getSidechainSigner() + const signer = await this.ethereum.getSidechainSigner() const address = await signer.getAddress() const duSidechain = await getSidechainContract(this, options) @@ -1039,7 +1039,7 @@ export async function withdrawTo(recipientAddress, options = {}) { * @returns {Promise} await on call .wait to actually send the tx */ export async function getWithdrawTxTo(recipientAddress, options) { - const signer = await this.getSidechainSigner() + const signer = await this.ethereum.getSidechainSigner() const address = await signer.getAddress() const duSidechain = await getSidechainContract(this, options) const withdrawable = await duSidechain.getWithdrawableEarnings(address) @@ -1078,7 +1078,7 @@ export async function signWithdrawTo(recipientAddress, options) { */ export async function signWithdrawAmountTo(recipientAddress, amountTokenWei, options) { const to = getAddress(recipientAddress) // throws if bad address - const signer = this.getSigner() // it shouldn't matter if it's mainnet or sidechain signer since key should be the same + const signer = this.ethereum.getSigner() // it shouldn't matter if it's mainnet or sidechain signer since key should be the same const address = await signer.getAddress() const duSidechain = await getSidechainContractReadOnly(this, options) const memberData = await duSidechain.memberData(address) From 83ee19ff3e50d17c0e1b4048aac38c4235c37698 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 20 Jan 2021 17:10:29 -0500 Subject: [PATCH 380/517] Add metamask prompt bug workaround. --- src/publish/Signer.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/publish/Signer.js b/src/publish/Signer.js index 8f28c7a03..1b7ece7cd 100644 --- a/src/publish/Signer.js +++ b/src/publish/Signer.js @@ -1,6 +1,8 @@ import { MessageLayer, Utils } from 'streamr-client-protocol' import { Web3Provider } from '@ethersproject/providers' +import { pLimitFn, sleep } from '../utils' + const { StreamMessage } = MessageLayer const { SigningUtil } = Utils const { SIGNATURE_TYPES } = StreamMessage @@ -16,7 +18,13 @@ function getSigningFunction({ privateKey, ethereum } = {}) { if (ethereum) { const web3Provider = new Web3Provider(ethereum) const signer = web3Provider.getSigner() - return async (d) => signer.signMessage(d) + // sign one at a time & wait a moment before asking for next signature + // otherwise metamask extension may not show the prompt window + return pLimitFn(async (d) => { + const sig = await signer.signMessage(d) + await sleep(50) + return sig + }, 1) } throw new Error('Need either "privateKey" or "ethereum".') @@ -55,8 +63,9 @@ export default function Signer(options = {}, publishWithSignature = 'auto') { // set signature so getting of payload works correctly // (publisherId should already be set) streamMessage.signatureType = signatureType // eslint-disable-line no-param-reassign + const signature = await sign(streamMessage.getPayloadToSign()) return Object.assign(streamMessage, { - signature: await sign(streamMessage.getPayloadToSign()), + signature, }) } From a8db7683eb56a9f01f2f9ff8ccae74d84bd2e842 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 20 Jan 2021 17:16:04 -0500 Subject: [PATCH 381/517] Release v5.0.0-beta.3 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4dc973ab7..86e5a8d48 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.2", + "version": "5.0.0-beta.3", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index e5394d7c0..86117c290 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.2", + "version": "5.0.0-beta.3", "description": "JavaScript client library for Streamr", "repository": { "type": "git", From 5b7b01928150df44e415befcfeb071ada68c3b3c Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 21 Jan 2021 14:11:07 -0500 Subject: [PATCH 382/517] Initial pass on Readme. --- README.md | 457 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 241 insertions(+), 216 deletions(-) diff --git a/README.md b/README.md index bc420a532..4c420648e 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ Streamr JavaScript Client - By using this client, you can easily interact with the [Streamr](https://streamr.network) API from JavaScript-based environments, such as browsers and [node.js](https://nodejs.org). You can, for example, subscribe to real-time data in streams, produce new data to streams, and create new streams. The client uses websockets for producing and consuming messages to/from streams. It should work in all modern browsers. @@ -15,12 +14,13 @@ The client uses websockets for producing and consuming messages to/from streams. [![Build Status](https://travis-ci.com/streamr-dev/streamr-client-javascript.svg?branch=master)](https://travis-ci.com/streamr-dev/streamr-client-javascript) ## Breaking changes notice -- Dec 31st 2020: Email/password login will be removed. **Connect your email account to an Ethereum address before the deadline!** -- Dec 31st 2020: Stream storage becomes opt-in and freely choosable. -- Dec 31st 2020: Support for API keys will end. -- Date TBD: Support for unsigned data will be dropped. -[Installation](#installation) · [Usage](#usage) · [Client options](#client-options) · [Authentication options](#authentication-options) · [Message handler callback](#message-handler-callback) · [StreamrClient object](#streamrclient-object) · [Stream object](#stream-object) · [Subscription options](#subscription-options) · [Data Unions](#data-unions) · [Utility functions](#utility-functions) · [Events](#binding-to-events) · [Partitioning](#partitioning) · [Logging](#logging) · [NPM Publishing](#publishing-latest) +* Dec 31st 2020: Email/password login will be removed. **Connect your email account to an Ethereum address before the deadline!** +* Dec 31st 2020: Stream storage becomes opt-in and freely choosable. +* Dec 31st 2020: Support for API keys will end. +* Date TBD: Support for unsigned data will be dropped. + +[Installation](#installation) · [Usage](#usage) · [Client options](#client-options) · [Authentication options](#authentication-options) · [Message handler callback](#message-handler-callback) · [Connecting](#connecting) · [Stream object](#stream-object) · [Subscription options](#subscription-options) · [Data Unions](#data-unions) · [Utility functions](#utility-functions) · [Events](#events) · [Partitioning](#partitioning) · [Logging](#logging) · [For Developers](#for-developers) ## Installation @@ -34,9 +34,9 @@ Here are some quick examples. More detailed examples for the browser and node.js If you don't have an Ethereum account you can use the utility function `StreamrClient.generateEthereumAccount()`, which returns the address and private key of a fresh Ethereum account. -#### Creating a StreamrClient instance +### Creating a StreamrClient instance -```javascript +```js const client = new StreamrClient({ auth: { privateKey: 'your-private-key' @@ -46,60 +46,52 @@ const client = new StreamrClient({ When using Node.js remember to require the library with: -```javascript +```js const StreamrClient = require('streamr-client') ``` -#### Subscribing to real-time events in a stream +### Subscribing to real-time events in a stream -```javascript -const sub = client.subscribe( - { - stream: 'streamId', - partition: 0, // Optional, defaults to zero. Use for partitioned streams to select partition. - // optional resend options here - }, - (message, metadata) => { - // This is the message handler which gets called for every incoming message in the stream. - // Do something with the message here! - } -) +```js +const sub = await client.subscribe({ + stream: 'streamId', + partition: 0, // Optional, defaults to zero. Use for partitioned streams to select partition. + // optional resend options here +}, (message, metadata) => { + // This is the message handler which gets called for every incoming message in the stream. + // Do something with the message here! +}) ``` -#### Resending historical data +### Resending historical data -```javascript -const sub = await client.resend( - { - stream: 'streamId', - resend: { - last: 5, - }, +```js +const sub = await client.resend({ + stream: 'streamId', + resend: { + last: 5, }, - (message) => { - // This is the message handler which gets called for every received message in the stream. - // Do something with the message here! - } -) +}, (message) => { + // This is the message handler which gets called for every received message in the stream. + // Do something with the message here! +}) ``` See "Subscription options" for resend options -#### Programmatically creating a stream +### Programmatically creating a stream -```javascript -client.getOrCreateStream({ +```js +const stream = await client.getOrCreateStream({ name: 'My awesome stream created via the API', }) - .then((stream) => { - console.log(`Stream ${stream.id} has been created!`) - // Do something with the stream, for example call stream.publish(message) - }) +console.log(`Stream ${stream.id} has been created!`) +// Do something with the stream, for example call stream.publish(message) ``` -#### Publishing data points to a stream +### Publishing data points to a stream -```javascript +```js // Here's our example data point const msg = { temperature: 25.4, @@ -108,25 +100,25 @@ const msg = { } // Publish using the stream id only -client.publish('my-stream-id', msg) +await client.publish('my-stream-id', msg) // The first argument can also be the stream object -client.publish(stream, msg) +await client.publish(stream, msg) // Publish with a specific timestamp as a Date object (default is now) -client.publish('my-stream-id', msg, new Date(54365472)) +await client.publish('my-stream-id', msg, new Date(54365472)) // Publish with a specific timestamp in ms -client.publish('my-stream-id', msg, 54365472) +await client.publish('my-stream-id', msg, 54365472) // Publish with a specific timestamp as a ISO8601 string -client.publish('my-stream-id', msg, '2019-01-01T00:00:00.123Z') +await client.publish('my-stream-id', msg, '2019-01-01T00:00:00.123Z') // Publish with a specific partition key (read more about partitioning further down this readme) -client.publish('my-stream-id', msg, Date.now(), 'my-partition-key') +await client.publish('my-stream-id', msg, Date.now(), 'my-partition-key') // For convenience, stream.publish(...) equals client.publish(stream, ...) -stream.publish(msg) +await stream.publish(msg) ``` ## Client options @@ -149,16 +141,16 @@ stream.publish(msg) ## Authentication options -**Authenticating with an API key has been deprecated.** +Note: **Authenticating with an API key has been deprecated.** -**Support for email/password authentication will be dropped in the future and cryptographic keys/wallets will be the only supported method.** +Note: **Support for email/password authentication will be dropped in the future and cryptographic keys/wallets will be the only supported method.** If you don't have an Ethereum account you can use the utility function `StreamrClient.generateEthereumAccount()`, which returns the address and private key of a fresh Ethereum account. Authenticating with Ethereum also automatically creates an associated Streamr user, even if it doesn't already exist. Under the hood, the client will cryptographically sign a challenge to authenticate you as a Streamr user: -```javascript -new StreamrClient({ +```js +const client = new StreamrClient({ auth: { privateKey: 'your-private-key' } @@ -167,139 +159,170 @@ new StreamrClient({ Authenticating with an Ethereum private key contained in an Ethereum (web3) provider: -```javascript -new StreamrClient({ +```js +const client = new StreamrClient({ auth: { - provider: window.ethereum, + ethereum: window.ethereum, } }) ``` (Authenticating with a pre-existing session token, for internal use by the Streamr app): -```javascript -new StreamrClient({ +```js +const client = new StreamrClient({ auth: { sessionToken: 'session-token' } }) ``` -## Message handler callback +## Connecting -The second argument to `client.subscribe(options, callback)` is the callback function that will be called for each message as they arrive. Its arguments are as follows: +By default the client will automatically connect and disconnect as needed, ideally you should not need to manage connection state explicitly. +Specifically, it will automatically connect when you publish or subscribe, and automatically disconnect once all subscriptions are removed and no +messages were recently published. This behaviour can be disabled using the `autoConnect` & `autoDisconnect` options when creating a `new +StreamrClient`. Explicit calls to either `connect()` or `disconnect()` will disable all `autoConnect` & `autoDisconnect` functionality, but they can +be re-enabled by calling `enableAutoConnect()` or `enableAutoDisconnect()`. -| Argument | Description | -| :------------- | :------------------------------------------------------------ | -| payload | A JS object containing the message payload itself | -| streamMessage | The whole [StreamMessage](https://github.com/streamr-dev/streamr-client-protocol-js/blob/master/src/protocol/message_layer/StreamMessage.js) object containing various metadata, for example `streamMessage.getTimestamp()` etc. | - -## StreamrClient object - -#### Connecting +Calls that need a connection, such as `publish` or `subscribe` will fail with an error if you are disconnected and autoConnect is disabled. -| Name | Description | +| Name | Description | | :-------------------- | :------------------------------------------------------------ | -| connect() | Connects to the server, and also subscribes to any streams for which `subscribe()` has been called before calling `connect()`. Returns a Promise. Rejects if already connected or connecting. | -| disconnect() | Disconnects from the server, clearing all subscriptions. Returns a Promise. Rejects if already disconnected or disconnecting. | -| pause() | Disconnects from the server without clearing subscriptions. | -| ensureConnected() | Safely connects if not connected. Returns a promise. Resolves immediately if already connected. Only rejects if an error occurs during connection. | -| ensureDisconnected() | Safely disconnects if not disconnected. Returns a promise. Resolves immediately if already disconnected. Only rejects if an error occurs during disconnection. | +| connect() | Safely connects if not connected. Returns a promise. Resolves immediately if already connected. Only rejects if an error occurs during connection. | +| disconnect() | Safely disconnects if not already disconnected, clearing all subscriptions. Returns a Promise. Resolves immediately if already disconnected. Only rejects if an error occurs during disconnection. | +| enableAutoConnect(enable = true) | Enables autoConnect if it wasn't already enabled. Does not connect immediately. Use `enableAutoConnect(false)` to disable autoConnect. | +| enableAutoDisconnect(enable = true) | Enables autoDisconnect if it wasn't already enabled. Does not disconnect immediately. Use `enableAutoConnect(false)` to disable autoDisconnect.| -#### Managing subscriptions - -| Name | Description | -| :---------------------------- | :------------------------------------------------------------ | -| subscribe(options, callback) | Subscribes to a stream. Messages in this stream are passed to the `callback` function. See below for subscription options. Returns a `Subscription` object. | -| unsubscribe(Subscription) | Unsubscribes the given `Subscription`. | -| unsubscribeAll(`streamId`) | Unsubscribes all `Subscriptions` for `streamId`. | -| getSubscriptions(`streamId`) | Returns a list of `Subscriptions` for `streamId`. | +```js +const client = new StreamrClient({ + auth: { + privateKey: 'your-private-key' + }, + autoConnect: false, + autoDisconnect: false, +}) -#### Stream API +await client.connect() +``` -All the below functions return a Promise which gets resolved with the result. +## Managing subscriptions -| Name | Description | -| :--------------------------------------------------- | :------------------------------------------------------------ | -| getStream(streamId) | Fetches a stream object from the API. | -| listStreams(query) | Fetches an array of stream objects from the API. For the query params, consult the [API docs](https://api-explorer.streamr.com). | -| getStreamByName(name) | Fetches a stream which exactly matches the given name. | -| createStream(properties) | Creates a stream with the given properties. For more information on the stream properties, consult the [API docs](https://api-explorer.streamr.com). | -| getOrCreateStream(properties) | Gets a stream with the id or name given in `properties`, or creates it if one is not found. | -| publish(streamId, message, timestamp, partitionKey) | Publishes a new message to the given stream. | +| Name | Description | +| :--------------------------- | :----------------------------------------------------------- | +| subscribe(options, callback) | Subscribes to a stream. Messages in this stream are passed to the `callback` function. See below for subscription options. Returns a Promise resolving a `Subscription` object. | +| unsubscribe(Subscription) | Unsubscribes the given `Subscription`. Returns a promise. | +| unsubscribeAll(`streamId`) | Unsubscribes all `Subscriptions` for `streamId`. Returns a promise. | +| getSubscriptions(`streamId`) | Returns a list of `Subscriptions` for `streamId`. Returns a promise. | -#### Listening to state changes of the client +### Message handler callback -on(eventName, function) | Binds a `function` to an event called `eventName` -once(eventName, function) | Binds a `function` to an event called `eventName`. It gets called once and then removed. -removeListener(eventName, function) | Unbinds the `function` from events called `eventName` +The second argument to `client.subscribe(options, callback)` is the callback function that will be called for each message as they arrive. Its arguments are as follows: -## Stream object +| Argument | Description | +| :------------- | :---------------------------------------------------------- | +| payload | A JS object containing the message payload itself | +| streamMessage | The whole [StreamMessage](https://github.com/streamr-dev/streamr-client-protocol-js/blob/master/src/protocol/message_layer/StreamMessage.js) object containing various metadata, for example `streamMessage.getTimestamp()` etc. | -All the below functions return a Promise which gets resolved with the result. +```js +const sub = await client.subscribe({ + streamId: 'my-stream-id', +}, (payload, streamMessage) => { + console.log({ + payload, streamMessage + }) +}) -| Name | Description | -| :----------------------------------------- | :------------------------------------------------------------ | -| update() | Updates the properties of this stream object by sending them to the API. | -| delete() | Deletes this stream. | -| getPermissions() | Returns the list of permissions for this stream. | -| hasPermission(operation, user) | Returns a permission object, or null if no such permission was found. Valid `operation` values for streams are: stream_get, stream_edit, stream_delete, stream_publish, stream_subscribe, and stream_share. `user` is the username of a user, or null for public permissions. | -| grantPermission(operation, user) | Grants the permission to do `operation` to `user`, which are defined as above. | -| revokePermission(permissionId) | Revokes a permission identified by its `id`. | -| detectFields() | Updates the stream field config (schema) to match the latest data point in the stream. | -| publish(message, timestamp, partitionKey) | Publishes a new message to this stream. | +``` -## Subscription options +### Subscription Options Note that only one of the resend options can be used for a particular subscription. The default functionality is to resend nothing, only subscribe to messages from the subscription moment onwards. | Name | Description | -| :--------- | :------------------------------------------------------------ | +| :-------- | :----------------------------------------------------------- | | stream | Stream id to subscribe to | | partition | Partition number to subscribe to. Defaults to partition 0. | | resend | Object defining the resend options. Below are examples of its contents. | | groupKeys | Object defining the group key as a hex string for each publisher id of the stream. | -```javascript +```js // Resend N most recent messages -resend: { - last: 10, -} +const sub1 = await client.subscribe({ + streamId: 'my-stream-id', + resend: { + last: 10, + } +}, onMessage) // Resend from a specific message reference up to the newest message -resend: { - from: { - timestamp: 12345, - sequenceNumber: 0, // optional +const sub2 = await client.subscribe({ + streamId: 'my-stream-id', + resend: { + from: { + timestamp: 12345, + sequenceNumber: 0, // optional + }, + publisher: 'publisherId', // optional + msgChainId: 'msgChainId', // optional } - publisher: 'publisherId', // optional - msgChainId: 'msgChainId', // optional -} +}, onMessage) // Resend a limited range of messages -resend: { - from: { - timestamp: 12345, - sequenceNumber: 0, // optional - }, - to: { - timestamp: 54321, - sequenceNumber: 0, // optional - }, - publisher: 'publisherId', // optional - msgChainId: 'msgChainId', // optional -} +const sub3 = await client.subscribe({ + streamId: 'my-stream-id', + resend: { + from: { + timestamp: 12345, + sequenceNumber: 0, // optional + }, + to: { + timestamp: 54321, + sequenceNumber: 0, // optional + }, + publisher: 'publisherId', // optional + msgChainId: 'msgChainId', // optional + } +}, onMessage) ``` If you choose one of the above resend options when subscribing, you can listen on the completion of this resend by doing the following: -```javascript -const sub = client.subscribe(...) -sub.on('initial_resend_done', () => { +```js +const sub = await client.subscribe(options) +sub.on('resent', () => { console.log('All caught up and received all requested historical messages! Now switching to real time!') }) ``` +## Stream API + +All the below functions return a Promise which gets resolved with the result. + +| Name | Description | +| :-------------------------------------------------- | :---------------------------------------------------------- | +| getStream(streamId) | Fetches a stream object from the API. | +| listStreams(query) | Fetches an array of stream objects from the API. For the query params, consult the [API docs](https://api-explorer.streamr.com). | +| getStreamByName(name) | Fetches a stream which exactly matches the given name. | +| createStream(properties) | Creates a stream with the given properties. For more information on the stream properties, consult the [API docs](https://api-explorer.streamr.com). | +| getOrCreateStream(properties) | Gets a stream with the id or name given in `properties`, or creates it if one is not found. | +| publish(streamId, message, timestamp, partitionKey) | Publishes a new message to the given stream. | + +### Stream object + +All the below functions return a Promise which gets resolved with the result. + +| Name | Description | +| :---------------------------------------- | :----------------------------------------------------------- | +| update() | Updates the properties of this stream object by sending them to the API. | +| delete() | Deletes this stream. | +| getPermissions() | Returns the list of permissions for this stream. | +| hasPermission(operation, user) | Returns a permission object, or null if no such permission was found. Valid `operation` values for streams are: stream_get, stream_edit, stream_delete, stream_publish, stream_subscribe, and stream_share. `user` is the username of a user, or null for public permissions. | +| grantPermission(operation, user) | Grants the permission to do `operation` to `user`, which are defined as above. | +| revokePermission(permissionId) | Revokes a permission identified by its `id`. | +| detectFields() | Updates the stream field config (schema) to match the latest data point in the stream. | +| publish(message, timestamp, partitionKey) | Publishes a new message to this stream. | + ## Data Unions This library provides functions for working with Data Unions. @@ -322,12 +345,12 @@ Data union functions take a third parameter, `options`, which are either overrid | sidechainAmbAddress | TODO | Arbitrary Message-passing Bridge (AMB), see [Tokenbridge github page](https://github.com/poanetwork/tokenbridge) | payForSignatureTransport | `true` | Someone must pay for transporting the withdraw tx to mainnet, either us or bridge operator -#### Admin functions +### Admin Functions | Name | Returns | Description | | :-------------------------------------------------------- | :-------------------- | :--------------------------------------------------------- | | deployDataUnion(options) | Dataunion contract | Deploy a new Data Union | -| createSecret(dataUnionContractAddress, secret[, name]) | | Create a secret for a Data Union | +| createSecret(dataUnionContractAddress, secret\[, name]) | | Create a secret for a Data Union | | addMembers(memberAddressList, options) | Transaction receipt | Add members | | kick(memberAddressList, options) | Transaction receipt | Kick members out from Data Union | | withdrawMember(memberAddress, options) ||| @@ -336,20 +359,21 @@ Data union functions take a third parameter, `options`, which are either overrid Here's an example how to deploy a data union contract and set the admin fee: -```javascript +```js const client = new StreamrClient({ auth: { privateKey }, }) + const dataUnion = await client.deployDataUnion() await client.setAdminFee(0.3, { dataUnion }) ``` -#### Member functions +### Member functions | Name | Returns | Description | | :---------------------------------------------------------------- | :------------ | :------------------------------------------------------------ | | joinDataUnion(options) | JoinRequest | Join a Data Union | -| hasJoined([memberAddress], options) | - | Wait until member has been accepted | +| hasJoined(\[memberAddress], options) | - | Wait until member has been accepted | | withdraw(options) | Transaction receipt | Withdraw funds from Data Union | | withdrawTo(recipientAddress, dataUnionContractAddress, options) | Transaction receipt | Donate/move your earnings to recipientAddress instead of your memberAddress | | signWithdrawTo(recipientAddress, options) | Signature (string) | Signature that can be used to withdraw tokens to given recipientAddress | @@ -357,37 +381,39 @@ await client.setAdminFee(0.3, { dataUnion }) Here's an example how to sign off on a withdraw to (any) recipientAddress: -```javascript +```js const client = new StreamrClient({ auth: { privateKey }, dataUnion, }) + const signature = await client.signWithdrawTo(recipientAddress) ``` -#### Query functions +### Query functions These are available for everyone and anyone, to query publicly available info from a Data Union: | Name | Returns | Description | | :-------------------------------------------------------- | :-------------------------------------- | :-------------------------- | -| getMemberStats(dataUnionContractAddress[, memberAddress]) | {earnings, proof, ...} | Get member's stats | +| getMemberStats(dataUnionContractAddress\[, memberAddress]) | {earnings, proof, ...} | Get member's stats | | getDataUnionStats(dataUnionContractAddress) | {activeMemberCount, totalEarnings, ...} | Get Data Union's statistics | | ~~getMembers(dataUnionContractAddress)~~ | | NOT available in DU2 at the moment | | getAdminFee(options) | `Number` between 0.0 and 1.0 (inclusive)| Admin's cut from revenues | | getAdminAddress(options) | Ethereum address | Data union admin's address | | getDataUnionStats(options) | Stats object | Various metrics from the smart contract | -| getMemberStats([memberAddress], options) | Member stats object | Various metrics from the smart contract | -| getMemberBalance([memberAddress], options) | `BigNumber` withdrawable DATA tokens in the DU | | +| getMemberStats(\[memberAddress], options) | Member stats object | Various metrics from the smart contract | +| getMemberBalance(\[memberAddress], options) | `BigNumber` withdrawable DATA tokens in the DU | | | getTokenBalance(address, options) | `BigNumber` | Mainnet DATA token balance | | getDataUnionVersion(contractAddress) | `0`, `1` or `2` | `0` if the contract is not a data union | Here's an example how to get a member's withdrawable token balance (in "wei", where 1 DATA = 10^18 wei) -```javascript +```js const client = new StreamrClient({ dataUnion, }) + const withdrawableWei = await client.getMemberBalance(memberAddress) ``` @@ -399,59 +425,57 @@ const withdrawableWei = await client.getMemberBalance(memberAddress) ## Events -#### Binding to events - -The client and the subscriptions can fire events as detailed below. You can bind to them using `on`: +The client and the subscriptions can fire events as detailed below. +You can bind to them using `on`. -```javascript - // The StreamrClient emits various events - client.on('connected', () => { - console.log('Yeah, we are connected now!') - }) - - // So does the Subscription object - const sub = client.subscribe(...) - sub.on('subscribed', () => { - console.log(`Subscribed to ${sub.streamId}`) - }) -``` +| Name | Description | +| :--------------------------------------- | :------------------------------------------------------------ | +on(eventName, function) | Binds a `function` to an event called `eventName` | +once(eventName, function) | Binds a `function` to an event called `eventName`. It gets called once and then removed. | +removeListener(eventName, function) | Unbinds the `function` from events called `eventName` | -#### Events on the StreamrClient instance +### Events on the StreamrClient instance | Name | Handler Arguments | Description | | :------------ | :----------------- | :----------------------------------------------------- | | connected | | Fired when the client has connected (or reconnected). | | disconnected | | Fired when the client has disconnected (or paused). | +| error | Error | Fired when the client encounters an error e.g. connection issues | + +```js +// The StreamrClient emits various events +client.on('connected', () => { + // note no need to wait for this before doing work, + // with autoconnect enabled the client will happily establish a connection for you as required. + console.log('Yeah, we are connected now!') +}) +``` -#### Events on the Subscription object +### Events on the Subscription object | Name | Handler Arguments | Description | | :------------ | :------------------------------------------------------------ | :------------------------------------------------------------ | -| subscribed | | Fired when a subscription request is acknowledged by the server. | | unsubscribed | | Fired when an unsubscription is acknowledged by the server. | -| resending | [ResendResponseResending](https://github.com/streamr-dev/streamr-client-protocol-js/blob/master/src/protocol/control_layer/resend_response_resending/ResendResponseResendingV1.js) | Fired when the subscription starts resending. Followed by the `resent` event to mark completion of the resend after resent messages have been processed by the message handler function. | -| resent | [ResendResponseResent](https://github.com/streamr-dev/streamr-client-protocol-js/blob/master/src/protocol/control_layer/resend_response_resent/ResendResponseResentV1.js) | Fired after `resending` when the subscription has finished resending. | -| no_resend | [ResendResponseNoResend](https://github.com/streamr-dev/streamr-client-protocol-js/blob/master/src/protocol/control_layer/resend_response_no_resend/ResendResponseNoResendV1.js) | This will occur instead of the `resending` - `resent` sequence in case there were no messages to resend. | +| resent | [ResendResponseResent](https://github.com/streamr-dev/streamr-client-protocol-js/blob/master/src/protocol/control_layer/resend_response_resent/ResendResponseResentV1.js) | Fired after `resending` when the subscription has finished resending and message has been processed | | error | Error object | Reports errors, for example problems with message content | ## Partitioning Partitioning (sharding) enables streams to scale horizontally. This section describes how to use partitioned streams via this library. To learn the basics of partitioning, see [the docs](https://streamr.network/docs/streams#partitioning). -#### Creating partitioned streams +### Creating partitioned streams By default, streams only have 1 partition when they are created. The partition count can be set to any positive number (1-100 is reasonable). An example of creating a partitioned stream using the JS client: -```javascript -client.createStream({ +```js +const stream = await client.createStream({ name: 'My partitioned stream', partitions: 10, -}).then(stream => { - console.log(`Stream created: ${stream.id}. It has ${stream.partitions} partitions.`) }) +console.log(`Stream created: ${stream.id}. It has ${stream.partitions} partitions.`) ``` -#### Publishing to partitioned streams +### Publishing to partitioned streams In most use cases, a user wants related events (e.g. events from a particular device) to be assigned to the same partition, so that the events retain a deterministic order and reach the same subscriber(s) to allow them to compute stateful aggregates correctly. @@ -459,45 +483,39 @@ The library allows the user to choose a *partition key*, which simplifies publis The partition key can be given as an argument to the `publish` methods, and the library assigns a deterministic partition number automatically: -```javascript -client.publish('my-stream-id', msg, Date.now(), msg.vehicleId) +```js +await client.publish('my-stream-id', msg, Date.now(), msg.vehicleId) // or, equivalently -stream.publish(msg, Date.now(), msg.vehicleId) +await stream.publish(msg, Date.now(), msg.vehicleId) ``` -#### Subscribing to partitioned streams +### Subscribing to partitioned streams By default, the JS client subscribes to the first partition (partition `0`) in a stream. The partition number can be explicitly given in the subscribe call: -```javascript -client.subscribe( - { - stream: 'my-stream-id', - partition: 4, // defaults to 0 - }, - (payload) => { - console.log(`Got message ${JSON.stringify(payload)}`) - }, -) +```js +const sub = await client.subscribe({ + stream: 'my-stream-id', + partition: 4, // defaults to 0 +}, (payload) => { + console.log('Got message %o', payload) +}) ``` Or, to subscribe to multiple partitions, if the subscriber can handle the volume: -```javascript +```js const handler = (payload, streamMessage) => { - console.log(`Got message ${JSON.stringify(payload)} from partition ${streamMessage.getStreamPartition()}`) + console.log('Got message %o from partition %d', payload, streamMessage.getStreamPartition()) } -[2,3,4].forEach(partition => { - client.subscribe( - { - stream: 'my-stream-id', - partition: partition, - }, - handler, - ) -}) +await Promise.all([2, 3, 4].map(async (partition) => { + await client.subscribe({ + stream: 'my-stream-id', + partition, + }, handler) +})) ``` ## Logging @@ -506,25 +524,32 @@ The Streamr JS client library supports [debug](https://github.com/visionmedia/de In node.js, start your app like this: `DEBUG=StreamrClient* node your-app.js` -In the browser, include `debug.js` and set `localStorage.debug = 'StreamrClient'` +In the browser, set `localStorage.debug = 'StreamrClient*'` + +## For Developers + +Publishing to npm is automated via Github Actions. Follow the steps below to publish `latest` or `beta`. + +### Publishing `latest` + +1. Update version with either `npm version [patch|minor|major]`. Use + semantic versioning https://semver.org/. Files package.json and + package-lock.json will be automatically updated, and an appropriate + git commit and tag created. + +2. `git push --follow-tags` +3. Wait for Github Actions to run tests -## Publishing +4. If tests passed, Github Actions will publish the new version to npm -Publishing to NPM is automated via Github Actions. Follow the steps below to publish `latest` or `beta`. +### Publishing `beta` -#### Publishing `latest`: +1. Update version with either `npm version [prepatch|preminor|premajor] --preid=beta`. Use semantic versioning + https://semver.org/. Files package.json and package-lock.json will be automatically updated, and an appropriate git commit and tag created. -1. Update version with either `npm version [patch|minor|major]`. Use semantic versioning - https://semver.org/. Files package.json and package-lock.json will be automatically updated, and an appropriate git commit and tag created. -2. `git push --follow-tags` -3. Wait for Github Actions to run tests -4. If tests passed, Github Actions will publish the new version to NPM +2. `git push --follow-tags` -#### Publishing `beta`: +3. Wait for Github Actions to run tests -1. Update version with either `npm version [prepatch|preminor|premajor] --preid=beta`. Use semantic versioning - https://semver.org/. Files package.json and package-lock.json will be automatically updated, and an appropriate git commit and tag created. -2. `git push --follow-tags` -3. Wait for Github Actions to run tests -4. If tests passed, Github Actions will publish the new version to NPM +4. If tests passed, Github Actions will publish the new version to npm From 33679800d5247068cd1f449f7e8abce18e579e17 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 21 Jan 2021 14:49:25 -0500 Subject: [PATCH 383/517] Tidy tables in README. --- README.md | 220 +++++++++++++++++++++++++++--------------------------- 1 file changed, 110 insertions(+), 110 deletions(-) diff --git a/README.md b/README.md index 4c420648e..a479c6720 100644 --- a/README.md +++ b/README.md @@ -123,21 +123,21 @@ await stream.publish(msg) ## Client options -| Option | Default value | Description | -| :------------------------ | :------------------------------- | :------------------------------------------------------------ | -| url | wss://streamr.network/api/v1/ws | Address of the Streamr websocket endpoint to connect to. | -| restUrl | https://streamr.network/api/v1 | Base URL of the Streamr REST API. | -| auth | {} | Object that can contain different information to authenticate. More details below. | -| publishWithSignature | 'auto' | Determines if data points published to streams are signed or not. Possible values are: 'auto', 'always' and 'never'. Signing requires `auth.privateKey` or `auth.provider`. 'auto' will sign only if one of them is set. 'always' will throw an exception if none of them is set. | -| verifySignatures | 'auto' | Determines under which conditions signed and unsigned data points are accepted or rejected. 'always' accepts only signed and verified data points. 'never' accepts all data points. 'auto' verifies all signed data points before accepting them and accepts unsigned data points only for streams not supposed to contain signed data. | -| autoConnect | true | If set to `true`, the client connects automatically on the first call to `subscribe()`. Otherwise an explicit call to `connect()` is required. | -| autoDisconnect | true | If set to `true`, the client automatically disconnects when the last stream is unsubscribed. Otherwise the connection is left open and can be disconnected explicitly by calling `disconnect()`. | -| orderMessages | true | If set to `true`, the subscriber handles messages in the correct order, requests missing messages and drops duplicates. Otherwise, the subscriber processes messages as they arrive without any check. | -| maxPublishQueueSize | 10000 | Only in effect when `autoConnect = true`. Controls the maximum number of messages to retain in internal queue when client has disconnected and is reconnecting to Streamr. | -| publisherGroupKeys | {} | Object defining the group key as a hex string used to encrypt for each stream id. | -| publisherStoreKeyHistory | true | If `true`, the client will locally store every key used to encrypt messages at some point. If set to `false`, the client will not be able to answer subscribers asking for historical keys during resend requests. | -| subscriberGroupKeys | {} | Object defining, for each stream id, an object containing the group key used to decrypt for each publisher id. Not needed if `keyExchange` is defined. | -| keyExchange | {} | Defines RSA key pair to use for group key exchange. Can define `publicKey` and `privateKey` fields as strings in the PEM format, or stay empty to generate a key pair automatically. Can be set to `null` if no key exchange is required. | +| Option | Default value | Description | +| :----------------------- | :------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| url | wss://streamr.network/api/v1/ws | Address of the Streamr websocket endpoint to connect to. | +| restUrl | | Base URL of the Streamr REST API. | +| auth | {} | Object that can contain different information to authenticate. More details below. | +| publishWithSignature | 'auto' | Determines if data points published to streams are signed or not. Possible values are: 'auto', 'always' and 'never'. Signing requires `auth.privateKey` or `auth.provider`. 'auto' will sign only if one of them is set. 'always' will throw an exception if none of them is set. | +| verifySignatures | 'auto' | Determines under which conditions signed and unsigned data points are accepted or rejected. 'always' accepts only signed and verified data points. 'never' accepts all data points. 'auto' verifies all signed data points before accepting them and accepts unsigned data points only for streams not supposed to contain signed data. | +| autoConnect | true | If set to `true`, the client connects automatically on the first call to `subscribe()`. Otherwise an explicit call to `connect()` is required. | +| autoDisconnect | true | If set to `true`, the client automatically disconnects when the last stream is unsubscribed. Otherwise the connection is left open and can be disconnected explicitly by calling `disconnect()`. | +| orderMessages | true | If set to `true`, the subscriber handles messages in the correct order, requests missing messages and drops duplicates. Otherwise, the subscriber processes messages as they arrive without any check. | +| maxPublishQueueSize | 10000 | Only in effect when `autoConnect = true`. Controls the maximum number of messages to retain in internal queue when client has disconnected and is reconnecting to Streamr. | +| publisherGroupKeys | {} | Object defining the group key as a hex string used to encrypt for each stream id. | +| publisherStoreKeyHistory | true | If `true`, the client will locally store every key used to encrypt messages at some point. If set to `false`, the client will not be able to answer subscribers asking for historical keys during resend requests. | +| subscriberGroupKeys | {} | Object defining, for each stream id, an object containing the group key used to decrypt for each publisher id. Not needed if `keyExchange` is defined. | +| keyExchange | {} | Defines RSA key pair to use for group key exchange. Can define `publicKey` and `privateKey` fields as strings in the PEM format, or stay empty to generate a key pair automatically. Can be set to `null` if no key exchange is required. | ## Authentication options @@ -187,12 +187,12 @@ be re-enabled by calling `enableAutoConnect()` or `enableAutoDisconnect()`. Calls that need a connection, such as `publish` or `subscribe` will fail with an error if you are disconnected and autoConnect is disabled. -| Name | Description | -| :-------------------- | :------------------------------------------------------------ | -| connect() | Safely connects if not connected. Returns a promise. Resolves immediately if already connected. Only rejects if an error occurs during connection. | +| Name | Description | +| :---------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| connect() | Safely connects if not connected. Returns a promise. Resolves immediately if already connected. Only rejects if an error occurs during connection. | | disconnect() | Safely disconnects if not already disconnected, clearing all subscriptions. Returns a Promise. Resolves immediately if already disconnected. Only rejects if an error occurs during disconnection. | -| enableAutoConnect(enable = true) | Enables autoConnect if it wasn't already enabled. Does not connect immediately. Use `enableAutoConnect(false)` to disable autoConnect. | -| enableAutoDisconnect(enable = true) | Enables autoDisconnect if it wasn't already enabled. Does not disconnect immediately. Use `enableAutoConnect(false)` to disable autoDisconnect.| +| enableAutoConnect(enable = true) | Enables autoConnect if it wasn't already enabled. Does not connect immediately. Use `enableAutoConnect(false)` to disable autoConnect. | +| enableAutoDisconnect(enable = true) | Enables autoDisconnect if it wasn't already enabled. Does not disconnect immediately. Use `enableAutoConnect(false)` to disable autoDisconnect. | ```js const client = new StreamrClient({ @@ -208,20 +208,20 @@ await client.connect() ## Managing subscriptions -| Name | Description | -| :--------------------------- | :----------------------------------------------------------- | +| Name | Description | +| :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | subscribe(options, callback) | Subscribes to a stream. Messages in this stream are passed to the `callback` function. See below for subscription options. Returns a Promise resolving a `Subscription` object. | -| unsubscribe(Subscription) | Unsubscribes the given `Subscription`. Returns a promise. | -| unsubscribeAll(`streamId`) | Unsubscribes all `Subscriptions` for `streamId`. Returns a promise. | -| getSubscriptions(`streamId`) | Returns a list of `Subscriptions` for `streamId`. Returns a promise. | +| unsubscribe(Subscription) | Unsubscribes the given `Subscription`. Returns a promise. | +| unsubscribeAll(`streamId`) | Unsubscribes all `Subscriptions` for `streamId`. Returns a promise. | +| getSubscriptions(`streamId`) | Returns a list of `Subscriptions` for `streamId`. Returns a promise. | ### Message handler callback The second argument to `client.subscribe(options, callback)` is the callback function that will be called for each message as they arrive. Its arguments are as follows: -| Argument | Description | -| :------------- | :---------------------------------------------------------- | -| payload | A JS object containing the message payload itself | +| Argument | Description | +| :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| payload | A JS object containing the message payload itself | | streamMessage | The whole [StreamMessage](https://github.com/streamr-dev/streamr-client-protocol-js/blob/master/src/protocol/message_layer/StreamMessage.js) object containing various metadata, for example `streamMessage.getTimestamp()` etc. | ```js @@ -239,11 +239,11 @@ const sub = await client.subscribe({ Note that only one of the resend options can be used for a particular subscription. The default functionality is to resend nothing, only subscribe to messages from the subscription moment onwards. -| Name | Description | -| :-------- | :----------------------------------------------------------- | -| stream | Stream id to subscribe to | -| partition | Partition number to subscribe to. Defaults to partition 0. | -| resend | Object defining the resend options. Below are examples of its contents. | +| Name | Description | +| :-------- | :--------------------------------------------------------------------------------- | +| stream | Stream id to subscribe to | +| partition | Partition number to subscribe to. Defaults to partition 0. | +| resend | Object defining the resend options. Below are examples of its contents. | | groupKeys | Object defining the group key as a hex string for each publisher id of the stream. | ```js @@ -299,29 +299,29 @@ sub.on('resent', () => { All the below functions return a Promise which gets resolved with the result. -| Name | Description | -| :-------------------------------------------------- | :---------------------------------------------------------- | -| getStream(streamId) | Fetches a stream object from the API. | -| listStreams(query) | Fetches an array of stream objects from the API. For the query params, consult the [API docs](https://api-explorer.streamr.com). | -| getStreamByName(name) | Fetches a stream which exactly matches the given name. | +| Name | Description | +| :-------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------- | +| getStream(streamId) | Fetches a stream object from the API. | +| listStreams(query) | Fetches an array of stream objects from the API. For the query params, consult the [API docs](https://api-explorer.streamr.com). | +| getStreamByName(name) | Fetches a stream which exactly matches the given name. | | createStream(properties) | Creates a stream with the given properties. For more information on the stream properties, consult the [API docs](https://api-explorer.streamr.com). | -| getOrCreateStream(properties) | Gets a stream with the id or name given in `properties`, or creates it if one is not found. | -| publish(streamId, message, timestamp, partitionKey) | Publishes a new message to the given stream. | +| getOrCreateStream(properties) | Gets a stream with the id or name given in `properties`, or creates it if one is not found. | +| publish(streamId, message, timestamp, partitionKey) | Publishes a new message to the given stream. | ### Stream object All the below functions return a Promise which gets resolved with the result. -| Name | Description | -| :---------------------------------------- | :----------------------------------------------------------- | -| update() | Updates the properties of this stream object by sending them to the API. | -| delete() | Deletes this stream. | -| getPermissions() | Returns the list of permissions for this stream. | +| Name | Description | +| :---------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| update() | Updates the properties of this stream object by sending them to the API. | +| delete() | Deletes this stream. | +| getPermissions() | Returns the list of permissions for this stream. | | hasPermission(operation, user) | Returns a permission object, or null if no such permission was found. Valid `operation` values for streams are: stream_get, stream_edit, stream_delete, stream_publish, stream_subscribe, and stream_share. `user` is the username of a user, or null for public permissions. | -| grantPermission(operation, user) | Grants the permission to do `operation` to `user`, which are defined as above. | -| revokePermission(permissionId) | Revokes a permission identified by its `id`. | -| detectFields() | Updates the stream field config (schema) to match the latest data point in the stream. | -| publish(message, timestamp, partitionKey) | Publishes a new message to this stream. | +| grantPermission(operation, user) | Grants the permission to do `operation` to `user`, which are defined as above. | +| revokePermission(permissionId) | Revokes a permission identified by its `id`. | +| detectFields() | Updates the stream field config (schema) to match the latest data point in the stream. | +| publish(message, timestamp, partitionKey) | Publishes a new message to this stream. | ## Data Unions @@ -331,31 +331,31 @@ TODO: check all this documentation before merging/publishing, probably some of i Data union functions take a third parameter, `options`, which are either overrides to options given in the constructor, or optional arguments to the function. -| Property | Default | Description | -| :------------ | :-------------------- | :------------------------------------------------------------ | -| wallet | given in auth | ethers.js Wallet object to use to sign and send withdraw transaction | -| provider | mainnet | ethers.js Provider to use if wallet wasn't provided | -| confirmations | `1` | Number of blocks to wait after the withdraw transaction is mined | -| gasPrice | ethers.js supplied | Probably uses the network estimate -| dataUnion | - | Address or contract object of the data union that is the target of the operation -| tokenAddress | 0x0Cf0Ee637
88A0849fE52
97F3407f701
E122cC023 | Token used by the DU -| minimumWithdrawTokenWei | 1000000 | Threshold value set in AMB configs, smallest token amount that can pass over the bridge -| sidechainTokenAddress | TODO | sidechain token address -| factoryMainnetAddress | TODO | Data Union factory that creates a new Data Union -| sidechainAmbAddress | TODO | Arbitrary Message-passing Bridge (AMB), see [Tokenbridge github page](https://github.com/poanetwork/tokenbridge) -| payForSignatureTransport | `true` | Someone must pay for transporting the withdraw tx to mainnet, either us or bridge operator +| Property | Default | Description | +| :----------------------- | :----------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | +| wallet | given in auth | ethers.js Wallet object to use to sign and send withdraw transaction | +| provider | mainnet | ethers.js Provider to use if wallet wasn't provided | +| confirmations | `1` | Number of blocks to wait after the withdraw transaction is mined | +| gasPrice | ethers.js supplied | Probably uses the network estimate | +| dataUnion | - | Address or contract object of the data union that is the target of the operation | +| tokenAddress | 0x0Cf0Ee637
88A0849fE52
97F3407f701
E122cC023 | Token used by the DU | +| minimumWithdrawTokenWei | 1000000 | Threshold value set in AMB configs, smallest token amount that can pass over the bridge | +| sidechainTokenAddress | TODO | sidechain token address | +| factoryMainnetAddress | TODO | Data Union factory that creates a new Data Union | +| sidechainAmbAddress | TODO | Arbitrary Message-passing Bridge (AMB), see [Tokenbridge github page](https://github.com/poanetwork/tokenbridge) | +| payForSignatureTransport | `true` | Someone must pay for transporting the withdraw tx to mainnet, either us or bridge operator | ### Admin Functions -| Name | Returns | Description | -| :-------------------------------------------------------- | :-------------------- | :--------------------------------------------------------- | -| deployDataUnion(options) | Dataunion contract | Deploy a new Data Union | -| createSecret(dataUnionContractAddress, secret\[, name]) | | Create a secret for a Data Union | -| addMembers(memberAddressList, options) | Transaction receipt | Add members | -| kick(memberAddressList, options) | Transaction receipt | Kick members out from Data Union | -| withdrawMember(memberAddress, options) ||| -| withdrawToSigned(memberAddress, recipientAddress, signature, options) ||| -| setAdminFee(newFeeFraction, options) | Transaction receipt | `newFeeFraction` is a `Number` between 0.0 and 1.0 (inclusive) | +| Name | Returns | Description | +| :-------------------------------------------------------------------- | :------------------ | :------------------------------------------------------------- | +| deployDataUnion(options) | Dataunion contract | Deploy a new Data Union | +| createSecret(dataUnionContractAddress, secret\[, name]) | | Create a secret for a Data Union | +| addMembers(memberAddressList, options) | Transaction receipt | Add members | +| kick(memberAddressList, options) | Transaction receipt | Kick members out from Data Union | +| withdrawMember(memberAddress, options) | | | +| withdrawToSigned(memberAddress, recipientAddress, signature, options) | | | +| setAdminFee(newFeeFraction, options) | Transaction receipt | `newFeeFraction` is a `Number` between 0.0 and 1.0 (inclusive) | Here's an example how to deploy a data union contract and set the admin fee: @@ -370,14 +370,14 @@ await client.setAdminFee(0.3, { dataUnion }) ### Member functions -| Name | Returns | Description | -| :---------------------------------------------------------------- | :------------ | :------------------------------------------------------------ | -| joinDataUnion(options) | JoinRequest | Join a Data Union | -| hasJoined(\[memberAddress], options) | - | Wait until member has been accepted | -| withdraw(options) | Transaction receipt | Withdraw funds from Data Union | -| withdrawTo(recipientAddress, dataUnionContractAddress, options) | Transaction receipt | Donate/move your earnings to recipientAddress instead of your memberAddress | -| signWithdrawTo(recipientAddress, options) | Signature (string) | Signature that can be used to withdraw tokens to given recipientAddress | -| signWithdrawAmountTo(recipientAddress, amountTokenWei, options) | Signature (string) | Signature that can be used to withdraw tokens to given recipientAddress | +| Name | Returns | Description | +| :-------------------------------------------------------------- | :------------------ | :-------------------------------------------------------------------------- | +| joinDataUnion(options) | JoinRequest | Join a Data Union | +| hasJoined(\[memberAddress], options) | - | Wait until member has been accepted | +| withdraw(options) | Transaction receipt | Withdraw funds from Data Union | +| withdrawTo(recipientAddress, dataUnionContractAddress, options) | Transaction receipt | Donate/move your earnings to recipientAddress instead of your memberAddress | +| signWithdrawTo(recipientAddress, options) | Signature (string) | Signature that can be used to withdraw tokens to given recipientAddress | +| signWithdrawAmountTo(recipientAddress, amountTokenWei, options) | Signature (string) | Signature that can be used to withdraw tokens to given recipientAddress | Here's an example how to sign off on a withdraw to (any) recipientAddress: @@ -394,18 +394,18 @@ const signature = await client.signWithdrawTo(recipientAddress) These are available for everyone and anyone, to query publicly available info from a Data Union: -| Name | Returns | Description | -| :-------------------------------------------------------- | :-------------------------------------- | :-------------------------- | -| getMemberStats(dataUnionContractAddress\[, memberAddress]) | {earnings, proof, ...} | Get member's stats | -| getDataUnionStats(dataUnionContractAddress) | {activeMemberCount, totalEarnings, ...} | Get Data Union's statistics | -| ~~getMembers(dataUnionContractAddress)~~ | | NOT available in DU2 at the moment | -| getAdminFee(options) | `Number` between 0.0 and 1.0 (inclusive)| Admin's cut from revenues | -| getAdminAddress(options) | Ethereum address | Data union admin's address | -| getDataUnionStats(options) | Stats object | Various metrics from the smart contract | -| getMemberStats(\[memberAddress], options) | Member stats object | Various metrics from the smart contract | -| getMemberBalance(\[memberAddress], options) | `BigNumber` withdrawable DATA tokens in the DU | | -| getTokenBalance(address, options) | `BigNumber` | Mainnet DATA token balance | -| getDataUnionVersion(contractAddress) | `0`, `1` or `2` | `0` if the contract is not a data union | +| Name | Returns | Description | +| :--------------------------------------------------------- | :--------------------------------------------- | :-------------------------------------- | +| getMemberStats(dataUnionContractAddress\[, memberAddress]) | {earnings, proof, ...} | Get member's stats | +| getDataUnionStats(dataUnionContractAddress) | {activeMemberCount, totalEarnings, ...} | Get Data Union's statistics | +| ~~getMembers(dataUnionContractAddress)~~ | | NOT available in DU2 at the moment | +| getAdminFee(options) | `Number` between 0.0 and 1.0 (inclusive) | Admin's cut from revenues | +| getAdminAddress(options) | Ethereum address | Data union admin's address | +| getDataUnionStats(options) | Stats object | Various metrics from the smart contract | +| getMemberStats(\[memberAddress], options) | Member stats object | Various metrics from the smart contract | +| getMemberBalance(\[memberAddress], options) | `BigNumber` withdrawable DATA tokens in the DU | | +| getTokenBalance(address, options) | `BigNumber` | Mainnet DATA token balance | +| getDataUnionVersion(contractAddress) | `0`, `1` or `2` | `0` if the contract is not a data union | Here's an example how to get a member's withdrawable token balance (in "wei", where 1 DATA = 10^18 wei) @@ -419,8 +419,8 @@ const withdrawableWei = await client.getMemberBalance(memberAddress) ## Utility functions -| Name | Description | -| :--------------------------------------- | :------------------------------------------------------------ | +| Name | Description | +| :-------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | StreamrClient.generateEthereumAccount() | Generates a random Ethereum private key and returns an object with fields `address` and privateKey. Note that this private key can be used to authenticate to the Streamr API by passing it in the authentication options, as described earlier in this document. | ## Events @@ -428,19 +428,19 @@ const withdrawableWei = await client.getMemberBalance(memberAddress) The client and the subscriptions can fire events as detailed below. You can bind to them using `on`. -| Name | Description | -| :--------------------------------------- | :------------------------------------------------------------ | -on(eventName, function) | Binds a `function` to an event called `eventName` | -once(eventName, function) | Binds a `function` to an event called `eventName`. It gets called once and then removed. | -removeListener(eventName, function) | Unbinds the `function` from events called `eventName` | +| Name | Description | +| :---------------------------------- | :--------------------------------------------------------------------------------------- | +| on(eventName, function) | Binds a `function` to an event called `eventName` | +| once(eventName, function) | Binds a `function` to an event called `eventName`. It gets called once and then removed. | +| removeListener(eventName, function) | Unbinds the `function` from events called `eventName` | ### Events on the StreamrClient instance -| Name | Handler Arguments | Description | -| :------------ | :----------------- | :----------------------------------------------------- | -| connected | | Fired when the client has connected (or reconnected). | -| disconnected | | Fired when the client has disconnected (or paused). | -| error | Error | Fired when the client encounters an error e.g. connection issues | +| Name | Handler Arguments | Description | +| :----------- | :---------------- | :--------------------------------------------------------------- | +| connected | | Fired when the client has connected (or reconnected). | +| disconnected | | Fired when the client has disconnected (or paused). | +| error | Error | Fired when the client encounters an error e.g. connection issues | ```js // The StreamrClient emits various events @@ -453,11 +453,11 @@ client.on('connected', () => { ### Events on the Subscription object -| Name | Handler Arguments | Description | -| :------------ | :------------------------------------------------------------ | :------------------------------------------------------------ | -| unsubscribed | | Fired when an unsubscription is acknowledged by the server. | +| Name | Handler Arguments | Description | +| :----------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :-------------------------------------------------------------------------------------------------- | +| unsubscribed | | Fired when an unsubscription is acknowledged by the server. | | resent | [ResendResponseResent](https://github.com/streamr-dev/streamr-client-protocol-js/blob/master/src/protocol/control_layer/resend_response_resent/ResendResponseResentV1.js) | Fired after `resending` when the subscription has finished resending and message has been processed | -| error | Error object | Reports errors, for example problems with message content | +| error | Error object | Reports errors, for example problems with message content | ## Partitioning @@ -479,7 +479,7 @@ console.log(`Stream created: ${stream.id}. It has ${stream.partitions} partition In most use cases, a user wants related events (e.g. events from a particular device) to be assigned to the same partition, so that the events retain a deterministic order and reach the same subscriber(s) to allow them to compute stateful aggregates correctly. -The library allows the user to choose a *partition key*, which simplifies publishing to partitioned streams by not requiring the user to assign a partition number explicitly. The same partition key always maps to the same partition. In an IoT use case, the device id can be used as partition key; in user interaction data it could be the user id, and so on. +The library allows the user to choose a _partition key_, which simplifies publishing to partitioned streams by not requiring the user to assign a partition number explicitly. The same partition key always maps to the same partition. In an IoT use case, the device id can be used as partition key; in user interaction data it could be the user id, and so on. The partition key can be given as an argument to the `publish` methods, and the library assigns a deterministic partition number automatically: @@ -533,7 +533,7 @@ Publishing to npm is automated via Github Actions. Follow the steps below to pub ### Publishing `latest` 1. Update version with either `npm version [patch|minor|major]`. Use - semantic versioning https://semver.org/. Files package.json and + semantic versioning . Files package.json and package-lock.json will be automatically updated, and an appropriate git commit and tag created. @@ -546,7 +546,7 @@ Publishing to npm is automated via Github Actions. Follow the steps below to pub ### Publishing `beta` 1. Update version with either `npm version [prepatch|preminor|premajor] --preid=beta`. Use semantic versioning - https://semver.org/. Files package.json and package-lock.json will be automatically updated, and an appropriate git commit and tag created. + . Files package.json and package-lock.json will be automatically updated, and an appropriate git commit and tag created. 2. `git push --follow-tags` From 390d43baa8e66145c7b9b19d7e3a42e507ab259f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 21 Jan 2021 17:00:24 -0500 Subject: [PATCH 384/517] Add getPublisherId to client. --- src/StreamrClient.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/StreamrClient.js b/src/StreamrClient.js index b3314d257..799777f1f 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -315,6 +315,10 @@ export default class StreamrClient extends EventEmitter { return this.ethereum.getAddress() } + async getPublisherId() { + return this.getAddress() + } + static generateEthereumAccount() { return StreamrEthereum.generateEthereumAccount() } From fc8aadc9ef0bc14fc5c630c1c87ea1b5492c7bcd Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 22 Jan 2021 12:11:27 -0500 Subject: [PATCH 385/517] Fix typo in DU endpoints. --- src/rest/DataUnionEndpoints.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/rest/DataUnionEndpoints.js b/src/rest/DataUnionEndpoints.js index 414a97f74..fd0e666a4 100644 --- a/src/rest/DataUnionEndpoints.js +++ b/src/rest/DataUnionEndpoints.js @@ -591,9 +591,9 @@ export async function deployDataUnion(options = {}) { if (adminFee < 0 || adminFee > 1) { throw new Error('options.adminFeeFraction must be a number between 0...1, got: ' + adminFee) } const adminFeeBN = BigNumber.from((adminFee * 1e18).toFixed()) // last 2...3 decimals are going to be gibberish - const mainnetProvider = this.ethererum.getMainnetProvider() - const mainnetWallet = this.ethererum.getSigner() - const sidechainProvider = this.ethererum.getSidechainProvider() + const mainnetProvider = this.ethereum.getMainnetProvider() + const mainnetWallet = this.ethereum.getSigner() + const sidechainProvider = this.ethereum.getSidechainProvider() // parseAddress defaults to authenticated user (also if "owner" is not an address) const ownerAddress = parseAddress(this, owner) From 2e908093d859a33ed9de5e378c9fd3abdb6953e9 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 22 Jan 2021 12:13:01 -0500 Subject: [PATCH 386/517] Release v5.0.0-beta.4 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 86e5a8d48..14db63af2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.3", + "version": "5.0.0-beta.4", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 86117c290..a2e397088 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.3", + "version": "5.0.0-beta.4", "description": "JavaScript client library for Streamr", "repository": { "type": "git", From 12134e7386425f353937f5aec58925aacb89dc91 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 22 Jan 2021 12:30:01 -0500 Subject: [PATCH 387/517] Fix reference to auth.provider in Readme. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a479c6720..d45deba3c 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ await stream.publish(msg) | url | wss://streamr.network/api/v1/ws | Address of the Streamr websocket endpoint to connect to. | | restUrl | | Base URL of the Streamr REST API. | | auth | {} | Object that can contain different information to authenticate. More details below. | -| publishWithSignature | 'auto' | Determines if data points published to streams are signed or not. Possible values are: 'auto', 'always' and 'never'. Signing requires `auth.privateKey` or `auth.provider`. 'auto' will sign only if one of them is set. 'always' will throw an exception if none of them is set. | +| publishWithSignature | 'auto' | Determines if data points published to streams are signed or not. Possible values are: 'auto', 'always' and 'never'. Signing requires `auth.privateKey` or `auth.ethereum`. 'auto' will sign only if one of them is set. 'always' will throw an exception if none of them is set. | | verifySignatures | 'auto' | Determines under which conditions signed and unsigned data points are accepted or rejected. 'always' accepts only signed and verified data points. 'never' accepts all data points. 'auto' verifies all signed data points before accepting them and accepts unsigned data points only for streams not supposed to contain signed data. | | autoConnect | true | If set to `true`, the client connects automatically on the first call to `subscribe()`. Otherwise an explicit call to `connect()` is required. | | autoDisconnect | true | If set to `true`, the client automatically disconnects when the last stream is unsubscribed. Otherwise the connection is left open and can be disconnected explicitly by calling `disconnect()`. | From af0f13552a1949e9424e9f88dd49211577245065 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 22 Jan 2021 12:53:45 -0500 Subject: [PATCH 388/517] Fix injected env vars to not be wrapped in quotes, include version in jest build. --- jest.setup.js | 3 +++ webpack.config.js | 12 +++++------- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/jest.setup.js b/jest.setup.js index 62b35bddd..81b9b01b6 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,6 +1,8 @@ const Debug = require('debug') const GitRevisionPlugin = require('git-revision-webpack-plugin') +const pkg = require('./package.json') + if (process.env.DEBUG_CONSOLE) { // Use debug as console log // This prevents jest messing with console output @@ -17,6 +19,7 @@ module.exports = async () => { gitRevisionPlugin.branch(), ]) Object.assign(process.env, { + version: pkg.version, GIT_VERSION, GIT_COMMITHASH, GIT_BRANCH, diff --git a/webpack.config.js b/webpack.config.js index 5c8c198d7..6cfa15224 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -64,15 +64,13 @@ module.exports = (env, argv) => { extensions: ['.json', '.js'], }, plugins: [ - new webpack.EnvironmentPlugin({ - NODE_ENV: JSON.stringify(process.env.NODE_ENV), - }), gitRevisionPlugin, new webpack.EnvironmentPlugin({ - version: JSON.stringify(pkg.version), - GIT_VERSION: JSON.stringify(gitRevisionPlugin.version()), - GIT_COMMITHASH: JSON.stringify(gitRevisionPlugin.commithash()), - GIT_BRANCH: JSON.stringify(gitRevisionPlugin.branch()), + NODE_ENV: process.env.NODE_ENV, + version: pkg.version, + GIT_VERSION: gitRevisionPlugin.version(), + GIT_COMMITHASH: gitRevisionPlugin.commithash(), + GIT_BRANCH: gitRevisionPlugin.branch(), }) ] } From 80cf1577a508f88a978d1dd472313a6463475048 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 25 Jan 2021 09:58:04 -0500 Subject: [PATCH 389/517] Adjusting resend subscribe timing. --- src/subscribe/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/subscribe/index.js b/src/subscribe/index.js index b955bc438..5707c5c2c 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -526,7 +526,6 @@ export default class Subscriber { let resentCount const it = pipeline([ async function* HandleResends() { - await resendMessageStream.subscribe() // Inconvience here // emitting the resent event is a bit tricky in this setup because the subscription // doesn't know anything about the source of the messages @@ -554,6 +553,7 @@ export default class Subscriber { ], end) let msgCount = 0 + await resendMessageStream.subscribe() resendSubscribeSub = await this.subscribe({ ...options, afterSteps: [ From b74727efb1f7286d2af1c3fd68113c10cbf0b1d5 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 25 Jan 2021 10:00:25 -0500 Subject: [PATCH 390/517] Further adjusting resend subscribe timing. --- src/subscribe/index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/subscribe/index.js b/src/subscribe/index.js index 5707c5c2c..225dc3159 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -553,8 +553,8 @@ export default class Subscriber { ], end) let msgCount = 0 - await resendMessageStream.subscribe() - resendSubscribeSub = await this.subscribe({ + const resendTask = resendMessageStream.subscribe() + const realtimeTask = this.subscribe({ ...options, afterSteps: [ async function* detectEndOfResend(src) { @@ -573,6 +573,12 @@ export default class Subscriber { msgStream: it, }, onMessage) + // eslint-disable-next-line semi-style + ;[resendSubscribeSub] = await Promise.all([ + realtimeTask, + resendTask, + ]) + // attach additional utility functions return Object.assign(resendSubscribeSub, { realtime: realtimeMessageStream, From 785af1fe99779b736022b5b5addec59a5cc9930a Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 11 Jan 2021 09:49:52 -0500 Subject: [PATCH 391/517] Add failing multiple publishers + late subscriber test. --- test/integration/MultipleClients.test.js | 219 ++++++++++++++++++++++- 1 file changed, 217 insertions(+), 2 deletions(-) diff --git a/test/integration/MultipleClients.test.js b/test/integration/MultipleClients.test.js index a98fe586a..dad17ed4d 100644 --- a/test/integration/MultipleClients.test.js +++ b/test/integration/MultipleClients.test.js @@ -1,6 +1,6 @@ import { wait } from 'streamr-test-utils' -import { uid, fakePrivateKey } from '../utils' +import { describeRepeats, uid, fakePrivateKey, getPublishTestMessages } from '../utils' import StreamrClient from '../../src' import Connection from '../../src/Connection' @@ -16,7 +16,7 @@ const createClient = (opts = {}) => new StreamrClient({ ...opts, }) -describe('PubSub with multiple clients', () => { +describeRepeats('PubSub with multiple clients', () => { let stream let mainClient let otherClient @@ -102,6 +102,221 @@ describe('PubSub with multiple clients', () => { expect(receivedMessagesOther).toEqual([message]) }, 30000) + test('works with multiple publishers on one stream', async () => { + const onEnd = [] + async function createPublisher() { + const pubClient = createClient({ + auth: { + privateKey: fakePrivateKey(), + } + }) + onEnd.push(() => pubClient.disconnect()) + pubClient.on('error', getOnError(errors)) + const pubUser = await pubClient.getUserInfo() + await stream.grantPermission('stream_get', pubUser.username) + await stream.grantPermission('stream_publish', pubUser.username) + // needed to check last + await stream.grantPermission('stream_subscribe', pubUser.username) + await pubClient.session.getSessionToken() + await pubClient.connect() + return pubClient + } + + try { + await mainClient.session.getSessionToken() + await mainClient.connect() + + otherClient = createClient({ + auth: { + privateKey + } + }) + otherClient.on('error', getOnError(errors)) + await otherClient.session.getSessionToken() + const otherUser = await otherClient.getUserInfo() + await stream.grantPermission('stream_get', otherUser.username) + await stream.grantPermission('stream_subscribe', otherUser.username) + await otherClient.connect() + + const receivedMessagesOther = {} + const receivedMessagesMain = {} + // subscribe to stream from other client instance + await otherClient.subscribe({ + stream: stream.id, + }, (msg, streamMessage) => { + const msgs = receivedMessagesOther[streamMessage.getPublisherId()] || [] + msgs.push(msg) + receivedMessagesOther[streamMessage.getPublisherId()] = msgs + }) + + // subscribe to stream from main client instance + await mainClient.subscribe({ + stream: stream.id, + }, (msg, streamMessage) => { + const msgs = receivedMessagesMain[streamMessage.getPublisherId()] || [] + msgs.push(msg) + receivedMessagesMain[streamMessage.getPublisherId()] = msgs + }) + + /* eslint-disable no-await-in-loop */ + const publishers = [] + for (let i = 0; i < 3; i++) { + publishers.push(await createPublisher()) + } + /* eslint-enable no-await-in-loop */ + const published = {} + await Promise.all(publishers.map(async (pubClient) => { + const publisherId = await pubClient.getPublisherId() + const publishTestMessages = getPublishTestMessages(pubClient, { + stream, + waitForLast: true, + }) + await publishTestMessages(10, { + delay: 500 + Math.random() * 1500, + afterEach(msg) { + published[publisherId] = published[publisherId] || [] + published[publisherId].push(msg) + } + }) + })) + + await wait(5000) + + mainClient.debug('%j', { + published, + receivedMessagesMain, + receivedMessagesOther, + }) + + // eslint-disable-next-line no-inner-declarations + function checkMessages(received) { + for (const [key, msgs] of Object.entries(published)) { + expect(received[key]).toEqual(msgs) + } + } + + checkMessages(receivedMessagesMain) + checkMessages(receivedMessagesOther) + } finally { + await Promise.all(onEnd.map((fn) => fn())) + } + }, 40000) + + test('works with multiple publishers on one stream with late subscriber', async () => { + const onEnd = [] + async function createPublisher() { + const pubClient = createClient({ + auth: { + privateKey: fakePrivateKey(), + } + }) + onEnd.push(() => pubClient.disconnect()) + pubClient.on('error', getOnError(errors)) + const pubUser = await pubClient.getUserInfo() + await stream.grantPermission('stream_get', pubUser.username) + await stream.grantPermission('stream_publish', pubUser.username) + // needed to check last + await stream.grantPermission('stream_subscribe', pubUser.username) + await pubClient.session.getSessionToken() + await pubClient.connect() + return pubClient + } + + const published = {} + function checkMessages(received) { + for (const [key, msgs] of Object.entries(published)) { + expect(received[key]).toEqual(msgs) + } + } + + const MAX_MESSAGES = 10 + + try { + await mainClient.session.getSessionToken() + await mainClient.connect() + + otherClient = createClient({ + auth: { + privateKey + } + }) + otherClient.on('error', getOnError(errors)) + await otherClient.session.getSessionToken() + const otherUser = await otherClient.getUserInfo() + await stream.grantPermission('stream_get', otherUser.username) + await stream.grantPermission('stream_subscribe', otherUser.username) + await otherClient.connect() + + const receivedMessagesOther = {} + const receivedMessagesMain = {} + + // subscribe to stream from main client instance + const mainSub = await mainClient.subscribe({ + stream: stream.id, + }, (msg, streamMessage) => { + const msgs = receivedMessagesMain[streamMessage.getPublisherId()] || [] + msgs.push(msg) + receivedMessagesMain[streamMessage.getPublisherId()] = msgs + if (Object.values(receivedMessagesMain).every((m) => m.length === MAX_MESSAGES)) { + mainSub.end() + } + }) + + /* eslint-disable no-await-in-loop */ + const publishers = [] + for (let i = 0; i < 3; i++) { + publishers.push(await createPublisher()) + } + + let counter = 0 + /* eslint-enable no-await-in-loop */ + await Promise.all(publishers.map(async (pubClient) => { + const publisherId = await pubClient.getPublisherId() + const publishTestMessages = getPublishTestMessages(pubClient, { + stream, + waitForLast: true, + }) + await publishTestMessages(MAX_MESSAGES, { + delay: 500 + Math.random() * 1500, + async afterEach(pubMsg) { + published[publisherId] = published[publisherId] || [] + published[publisherId].push(pubMsg) + counter += 1 + if (counter === 3) { + // late subscribe to stream from other client instance + const otherSub = await otherClient.subscribe({ + stream: stream.id, + resend: { + last: 1000, + } + }, (msg, streamMessage) => { + const msgs = receivedMessagesOther[streamMessage.getPublisherId()] || [] + msgs.push(msg) + receivedMessagesOther[streamMessage.getPublisherId()] = msgs + if (msgs.length === MAX_MESSAGES) { + return otherSub.end() + } + }) + } + } + }) + })) + + await wait(15000) + + mainClient.debug('%j', { + published, + receivedMessagesMain, + receivedMessagesOther, + }) + + checkMessages(receivedMessagesMain) + checkMessages(receivedMessagesOther) + } finally { + await Promise.all(onEnd.map((fn) => fn())) + } + }, 60000) + test('disconnecting one client does not disconnect the other', async () => { otherClient = createClient({ auth: { From 0b61c4660f460b85e0a2eb3e2470ecc3ec2e2673 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 25 Jan 2021 15:09:22 -0500 Subject: [PATCH 392/517] Adjust multi-publisher, multi-subscriber tests into passing state. --- src/StreamrClient.js | 1 + src/utils/AggregatedError.js | 16 +++ src/utils/index.js | 21 ++- test/integration/MultipleClients.test.js | 159 ++++++++++------------- test/utils.js | 26 +++- 5 files changed, 124 insertions(+), 99 deletions(-) diff --git a/src/StreamrClient.js b/src/StreamrClient.js index 799777f1f..ac7a898b1 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.js @@ -103,6 +103,7 @@ export default class StreamrClient extends EventEmitter { super() this.id = counterId(`${this.constructor.name}:${uid}`) this.debug = Debug(this.id) + this.options = Config({ id: this.id, debug: this.debug, diff --git a/src/utils/AggregatedError.js b/src/utils/AggregatedError.js index 7d49610a2..d95a2baf6 100644 --- a/src/utils/AggregatedError.js +++ b/src/utils/AggregatedError.js @@ -16,6 +16,22 @@ export default class AggregatedError extends Error { } } + static fromAllSettled(results = [], errorMessage = '') { + const errs = results.map(({ reason }) => reason).filter(Boolean) + if (!errs.length) { + return undefined + } + + return new AggregatedError(errs, errorMessage) + } + + static throwAllSettled(results, errorMessage = '') { + const err = this.fromAllSettled(results, errorMessage) + if (err) { + throw err + } + } + /** * Handles 'upgrading' an existing error to an AggregatedError when necesary. */ diff --git a/src/utils/index.js b/src/utils/index.js index 22f6602b8..7299acc16 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -44,9 +44,9 @@ export function randomString(length = 20) { export const counterId = (() => { const MAX_PREFIXES = 256 - const counts = {} // possible we could switch this to WeakMap and pass functions or classes. + let counts = {} // possible we could switch this to WeakMap and pass functions or classes. let didWarn = false - return (prefix = 'ID') => { + const counterIdFn = (prefix = 'ID') => { // pedantic: wrap around if count grows too large counts[prefix] = (counts[prefix] + 1 || 0) % Number.MAX_SAFE_INTEGER @@ -61,6 +61,23 @@ export const counterId = (() => { return `${prefix}.${counts[prefix]}` } + + /** + * Clears counts for prefix or all if no prefix supplied. + * + * @param {string?} prefix + */ + counterIdFn.clear = (...args) => { + // check length to differentiate between clear(undefined) & clear() + if (args.length) { + const [prefix] = args + delete counts[prefix] + } else { + // clear all + counts = {} + } + } + return counterIdFn })() export function getVersionString() { diff --git a/test/integration/MultipleClients.test.js b/test/integration/MultipleClients.test.js index dad17ed4d..506fc1b66 100644 --- a/test/integration/MultipleClients.test.js +++ b/test/integration/MultipleClients.test.js @@ -1,7 +1,8 @@ import { wait } from 'streamr-test-utils' -import { describeRepeats, uid, fakePrivateKey, getPublishTestMessages } from '../utils' +import { describeRepeats, uid, fakePrivateKey, getWaitForStorage, getPublishTestMessages, addAfterFn } from '../utils' import StreamrClient from '../../src' +import { counterId } from '../../src/utils' import Connection from '../../src/Connection' import config from './config' @@ -23,6 +24,8 @@ describeRepeats('PubSub with multiple clients', () => { let privateKey let errors = [] + const runAfterTest = addAfterFn() + const getOnError = (errs) => jest.fn((err) => { errs.push(err) }) @@ -43,10 +46,6 @@ describeRepeats('PubSub with multiple clients', () => { }) afterEach(async () => { - if (stream) { - await stream.delete() - } - if (mainClient) { mainClient.debug('disconnecting after test') await mainClient.disconnect() @@ -102,15 +101,16 @@ describeRepeats('PubSub with multiple clients', () => { expect(receivedMessagesOther).toEqual([message]) }, 30000) - test('works with multiple publishers on one stream', async () => { - const onEnd = [] + describe('multiple publishers', () => { + const MAX_MESSAGES = 10 + async function createPublisher() { const pubClient = createClient({ auth: { privateKey: fakePrivateKey(), } }) - onEnd.push(() => pubClient.disconnect()) + runAfterTest(() => pubClient.disconnect()) pubClient.on('error', getOnError(errors)) const pubUser = await pubClient.getUserInfo() await stream.grantPermission('stream_get', pubUser.username) @@ -122,7 +122,16 @@ describeRepeats('PubSub with multiple clients', () => { return pubClient } - try { + // eslint-disable-next-line no-inner-declarations + function checkMessages(published, received) { + for (const [key, msgs] of Object.entries(published)) { + expect(received[key]).toEqual(msgs) + } + } + + test('works with multiple publishers on one stream', async () => { + // this creates two subscriber clients and multiple publisher clients + // all subscribing and publishing to same stream await mainClient.session.getSessionToken() await mainClient.connect() @@ -166,72 +175,32 @@ describeRepeats('PubSub with multiple clients', () => { /* eslint-enable no-await-in-loop */ const published = {} await Promise.all(publishers.map(async (pubClient) => { - const publisherId = await pubClient.getPublisherId() + const publisherId = (await pubClient.getPublisherId()).toLowerCase() + runAfterTest(() => { + counterId.clear(publisherId) // prevent overflows in counter + }) const publishTestMessages = getPublishTestMessages(pubClient, { stream, waitForLast: true, + waitForLastTimeout: 10000, + waitForLastCount: MAX_MESSAGES, + createMessage: () => ({ + value: counterId(publisherId), + }), }) - await publishTestMessages(10, { + published[publisherId] = await publishTestMessages(MAX_MESSAGES, { delay: 500 + Math.random() * 1500, - afterEach(msg) { - published[publisherId] = published[publisherId] || [] - published[publisherId].push(msg) - } }) })) - await wait(5000) + checkMessages(published, receivedMessagesMain) + checkMessages(published, receivedMessagesOther) + }, 40000) - mainClient.debug('%j', { - published, - receivedMessagesMain, - receivedMessagesOther, - }) - - // eslint-disable-next-line no-inner-declarations - function checkMessages(received) { - for (const [key, msgs] of Object.entries(published)) { - expect(received[key]).toEqual(msgs) - } - } - - checkMessages(receivedMessagesMain) - checkMessages(receivedMessagesOther) - } finally { - await Promise.all(onEnd.map((fn) => fn())) - } - }, 40000) - - test('works with multiple publishers on one stream with late subscriber', async () => { - const onEnd = [] - async function createPublisher() { - const pubClient = createClient({ - auth: { - privateKey: fakePrivateKey(), - } - }) - onEnd.push(() => pubClient.disconnect()) - pubClient.on('error', getOnError(errors)) - const pubUser = await pubClient.getUserInfo() - await stream.grantPermission('stream_get', pubUser.username) - await stream.grantPermission('stream_publish', pubUser.username) - // needed to check last - await stream.grantPermission('stream_subscribe', pubUser.username) - await pubClient.session.getSessionToken() - await pubClient.connect() - return pubClient - } - - const published = {} - function checkMessages(received) { - for (const [key, msgs] of Object.entries(published)) { - expect(received[key]).toEqual(msgs) - } - } - - const MAX_MESSAGES = 10 - - try { + test('works with multiple publishers on one stream with late subscriber', async () => { + // this creates two subscriber clients and multiple publisher clients + // all subscribing and publishing to same stream + // the otherClient subscribes after the 3rd message hits storage await mainClient.session.getSessionToken() await mainClient.connect() @@ -240,6 +209,11 @@ describeRepeats('PubSub with multiple clients', () => { privateKey } }) + + runAfterTest(() => { + otherClient.disconnect() + }) + otherClient.on('error', getOnError(errors)) await otherClient.session.getSessionToken() const otherUser = await otherClient.getUserInfo() @@ -258,7 +232,7 @@ describeRepeats('PubSub with multiple clients', () => { msgs.push(msg) receivedMessagesMain[streamMessage.getPublisherId()] = msgs if (Object.values(receivedMessagesMain).every((m) => m.length === MAX_MESSAGES)) { - mainSub.end() + mainSub.unsubscribe() } }) @@ -268,23 +242,38 @@ describeRepeats('PubSub with multiple clients', () => { publishers.push(await createPublisher()) } - let counter = 0 /* eslint-enable no-await-in-loop */ + let counter = 0 + const published = {} await Promise.all(publishers.map(async (pubClient) => { - const publisherId = await pubClient.getPublisherId() + const waitForStorage = getWaitForStorage(pubClient, { + stream, + timeout: 10000, + count: MAX_MESSAGES, + }) + + const publisherId = (await pubClient.getPublisherId()).toLowerCase() + runAfterTest(() => { + counterId.clear(publisherId) // prevent overflows in counter + }) const publishTestMessages = getPublishTestMessages(pubClient, { stream, waitForLast: true, + waitForLastTimeout: 10000, + waitForLastCount: MAX_MESSAGES, + createMessage: () => ({ + value: counterId(publisherId), + }), }) - await publishTestMessages(MAX_MESSAGES, { + + published[publisherId] = await publishTestMessages(MAX_MESSAGES, { delay: 500 + Math.random() * 1500, - async afterEach(pubMsg) { - published[publisherId] = published[publisherId] || [] - published[publisherId].push(pubMsg) + async afterEach(pubMsg, req) { counter += 1 if (counter === 3) { // late subscribe to stream from other client instance - const otherSub = await otherClient.subscribe({ + await waitForStorage(req) // make sure lastest message has hit storage + await otherClient.subscribe({ stream: stream.id, resend: { last: 1000, @@ -293,29 +282,15 @@ describeRepeats('PubSub with multiple clients', () => { const msgs = receivedMessagesOther[streamMessage.getPublisherId()] || [] msgs.push(msg) receivedMessagesOther[streamMessage.getPublisherId()] = msgs - if (msgs.length === MAX_MESSAGES) { - return otherSub.end() - } }) } } }) })) - - await wait(15000) - - mainClient.debug('%j', { - published, - receivedMessagesMain, - receivedMessagesOther, - }) - - checkMessages(receivedMessagesMain) - checkMessages(receivedMessagesOther) - } finally { - await Promise.all(onEnd.map((fn) => fn())) - } - }, 60000) + checkMessages(published, receivedMessagesMain) + checkMessages(published, receivedMessagesOther) + }, 60000) + }) test('disconnecting one client does not disconnect the other', async () => { otherClient = createClient({ diff --git a/test/utils.js b/test/utils.js index f60937452..9de442832 100644 --- a/test/utils.js +++ b/test/utils.js @@ -2,7 +2,7 @@ import { inspect } from 'util' import { wait } from 'streamr-test-utils' -import { pTimeout, counterId } from '../src/utils' +import { pTimeout, counterId, AggregatedError } from '../src/utils' import { validateOptions } from '../src/stream/utils' const crypto = require('crypto') @@ -44,6 +44,19 @@ export async function collect(iterator, fn = () => {}) { return received } +export function addAfterFn() { + const afterFns = [] + afterEach(async () => { + const fns = afterFns.slice() + afterFns.length = 0 + AggregatedError.throwAllSettled(await Promise.allSettled(fns.map((fn) => fn()))) + }) + + return (fn) => { + afterFns.push(fn) + } +} + export const Msg = (opts) => ({ value: uid('msg'), ...opts, @@ -61,7 +74,7 @@ export function getWaitForStorage(client, defaultOpts = {}) { /* eslint-disable no-await-in-loop */ return async (publishRequest, opts = {}) => { const { - streamId, streamPartition = 0, interval = 500, timeout = 5000, messageMatchFn = defaultMessageMatchFn + streamId, streamPartition = 0, interval = 500, timeout = 5000, count = 100, messageMatchFn = defaultMessageMatchFn } = validateOptions({ ...defaultOpts, ...opts, @@ -93,7 +106,7 @@ export function getWaitForStorage(client, defaultOpts = {}) { last = await client.getStreamLast({ streamId, streamPartition, - count: 3, + count, }) for (const lastMsg of last) { @@ -130,9 +143,11 @@ export function getPublishTestMessages(client, defaultOpts = {}) { delay = 100, timeout = 3500, waitForLast = false, // wait for message to hit storage + waitForLastCount, waitForLastTimeout, beforeEach = (m) => m, - afterEach = () => {} + afterEach = () => {}, + createMessage = Msg, } = validateOptions({ ...defaultOpts, ...opts, @@ -140,7 +155,7 @@ export function getPublishTestMessages(client, defaultOpts = {}) { const published = [] for (let i = 0; i < n; i++) { - const message = Msg() + const message = createMessage() // eslint-disable-next-line no-await-in-loop, no-loop-func await beforeEach(message) // eslint-disable-next-line no-await-in-loop, no-loop-func @@ -165,6 +180,7 @@ export function getPublishTestMessages(client, defaultOpts = {}) { streamId, streamPartition, timeout: waitForLastTimeout, + count: waitForLastCount, messageMatchFn(m, b) { return m.streamMessage.signature === b.signature } From 7458313c51f8ccf5c0f0d23d05446d61f8e86255 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 25 Jan 2021 15:39:12 -0500 Subject: [PATCH 393/517] Loosen test timeouts. Add more cleanup. --- package.json | 2 +- test/integration/MultipleClients.test.js | 17 ++++++++++++----- test/integration/StreamrClient.test.js | 4 ++-- test/integration/Subscriber.test.js | 21 +++++++++++++++++++++ test/utils.js | 4 ++-- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index a2e397088..f2411952d 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "test-unit": "jest test/unit --detectOpenHandles", "coverage": "jest --coverage", "test-integration": "jest --forceExit test/integration", - "test-integration-no-resend": "jest --testPathIgnorePatterns='resend|Resend' --testNamePattern='^((?!(resend|Resend|resent|Resent)).)*$' test/integration/*.test.js", + "test-integration-no-resend": "jest --testTimeout=10000 --testPathIgnorePatterns='resend|Resend' --testNamePattern='^((?!(resend|Resend|resent|Resent)).)*$' test/integration/*.test.js", "test-integration-resend": "jest --testTimeout=15000 --testNamePattern='(resend|Resend|resent|Resent)' test/integration/*.test.js", "test-integration-dataunions": "jest --testTimeout=15000 test/integration/DataUnionEndpoints", "test-flakey": "jest --forceExit test/flakey/*", diff --git a/test/integration/MultipleClients.test.js b/test/integration/MultipleClients.test.js index 506fc1b66..62436a04a 100644 --- a/test/integration/MultipleClients.test.js +++ b/test/integration/MultipleClients.test.js @@ -119,6 +119,10 @@ describeRepeats('PubSub with multiple clients', () => { await stream.grantPermission('stream_subscribe', pubUser.username) await pubClient.session.getSessionToken() await pubClient.connect() + + runAfterTest(async () => { + await pubClient.disconnect() + }) return pubClient } @@ -181,6 +185,7 @@ describeRepeats('PubSub with multiple clients', () => { }) const publishTestMessages = getPublishTestMessages(pubClient, { stream, + delay: 500 + Math.random() * 1500, waitForLast: true, waitForLastTimeout: 10000, waitForLastCount: MAX_MESSAGES, @@ -188,9 +193,7 @@ describeRepeats('PubSub with multiple clients', () => { value: counterId(publisherId), }), }) - published[publisherId] = await publishTestMessages(MAX_MESSAGES, { - delay: 500 + Math.random() * 1500, - }) + published[publisherId] = await publishTestMessages(MAX_MESSAGES) })) checkMessages(published, receivedMessagesMain) @@ -261,19 +264,19 @@ describeRepeats('PubSub with multiple clients', () => { waitForLast: true, waitForLastTimeout: 10000, waitForLastCount: MAX_MESSAGES, + delay: 500 + Math.random() * 1500, createMessage: () => ({ value: counterId(publisherId), }), }) published[publisherId] = await publishTestMessages(MAX_MESSAGES, { - delay: 500 + Math.random() * 1500, async afterEach(pubMsg, req) { counter += 1 if (counter === 3) { // late subscribe to stream from other client instance await waitForStorage(req) // make sure lastest message has hit storage - await otherClient.subscribe({ + const lateSub = await otherClient.subscribe({ stream: stream.id, resend: { last: 1000, @@ -283,6 +286,10 @@ describeRepeats('PubSub with multiple clients', () => { msgs.push(msg) receivedMessagesOther[streamMessage.getPublisherId()] = msgs }) + + runAfterTest(async () => { + await lateSub.unsubscribe() + }) } } }) diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index 1f82319b4..edd1b81b4 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -475,7 +475,7 @@ describeRepeats('StreamrClient', () => { // key exchange stream subscription should not have been sent yet expect(connectionEventSpy.mock.calls.length).toEqual(1) await client.disconnect() - }, 5000) + }, 10000) it('does not try to reconnect', async () => { client = createClient() @@ -499,7 +499,7 @@ describeRepeats('StreamrClient', () => { await wait(2000) expect(onConnecting).toHaveBeenCalledTimes(0) expect(client.isConnected()).toBe(false) - }, 6000) + }, 10000) }) describe('publish/subscribe connection handling', () => { diff --git a/test/integration/Subscriber.test.js b/test/integration/Subscriber.test.js index 76529aa45..fdc7ac1ea 100644 --- a/test/integration/Subscriber.test.js +++ b/test/integration/Subscriber.test.js @@ -528,6 +528,27 @@ describeRepeats('StreamrClient Stream', () => { expect(M.count(stream.id)).toBe(0) }) + it('can subscribe to stream multiple times then unsubscribe one mid-stream', async () => { + let sub2ReceivedAtUnsubscribe + const [received1, received2] = await Promise.all([ + collect(sub1, async ({ received, iterator }) => { + if (received.length === published.length) { + await iterator.return() + } + }), + collect(sub2, async ({ received }) => { + if (received.length === MAX_ITEMS) { + sub2ReceivedAtUnsubscribe = received.slice() + await sub2.unsubscribe() + } + }), + ]) + expect(received2).toEqual(published.slice(0, MAX_ITEMS)) + expect(received1).toEqual(published) + expect(sub2ReceivedAtUnsubscribe).toEqual(received2) + expect(M.count(stream.id)).toBe(0) + }) + it('can subscribe to stream multiple times then return mid-stream', async () => { const [received1, received2] = await Promise.all([ collect(sub1, async ({ received, iterator }) => { diff --git a/test/utils.js b/test/utils.js index 9de442832..68fa3f0ec 100644 --- a/test/utils.js +++ b/test/utils.js @@ -25,14 +25,14 @@ export function describeRepeats(msg, fn, describeFn = describe) { } describeRepeats.skip = (msg, fn) => { - describe.skip(`test repeat ALL of ${TEST_REPEATS}`, fn) + describe.skip(`${msg} – test repeat ALL of ${TEST_REPEATS}`, fn) } describeRepeats.only = (msg, fn) => { describeRepeats(msg, fn, describe.only) } -export async function collect(iterator, fn = () => {}) { +export async function collect(iterator, fn = async () => {}) { const received = [] for await (const msg of iterator) { received.push(msg.getParsedContent()) From 7984a1325064bad138cf01eae6f6dbef3f9dacc8 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 25 Jan 2021 15:56:24 -0500 Subject: [PATCH 394/517] Test client fills gaps between resend & realtime. --- test/integration/GapFill.test.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/integration/GapFill.test.js b/test/integration/GapFill.test.js index 495718c35..c6aee66d4 100644 --- a/test/integration/GapFill.test.js +++ b/test/integration/GapFill.test.js @@ -201,5 +201,35 @@ describeRepeats('GapFill', () => { expect(received).toEqual(published) expect(client.connection.getState()).toBe('connected') }, 15000) + + it('can fill gaps between resend and realtime', async () => { + // publish 5 messages into storage + const published = await publishTestMessages(5, { + waitForLast: true, + waitForLastCount: 5, + }) + + // then simultaneously subscribe with resend & start publishing realtime messages + const [sub, publishedLater] = await Promise.all([ + client.subscribe({ + stream, + resend: { + last: 5 + } + }), + publishTestMessages(5) + ]) + + const received = [] + for await (const m of sub) { + received.push(m.getParsedContent()) + if (received.length === (published.length + publishedLater.length)) { + break + } + } + + expect(received).toEqual([...published, ...publishedLater]) + await sub.unsubscribe() + }, 15000) }) }) From a73dfc40d66e9967052573abcc8526fdd6d90574 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 25 Jan 2021 16:13:54 -0500 Subject: [PATCH 395/517] Remove --maxWorkers=1 in CI. --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 87ff30579..6a97ff029 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -106,7 +106,7 @@ jobs: with: services-to-start: "mysql redis engine-and-editor cassandra parity-node0 parity-sidechain-node0 bridge broker-node-storage-1 nginx smtp" - name: Run Test - run: npm run $TEST_NAME -- --maxWorkers=1 + run: npm run $TEST_NAME flakey: name: Flakey Tests using Node ${{ matrix.node-version }} From 57934131712251840f7b31620fb07b329d991d40 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 25 Jan 2021 16:17:28 -0500 Subject: [PATCH 396/517] Give resent event a chance to fire. --- test/integration/SubscriberResends.test.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/integration/SubscriberResends.test.js b/test/integration/SubscriberResends.test.js index 59054f3a7..e06b54601 100644 --- a/test/integration/SubscriberResends.test.js +++ b/test/integration/SubscriberResends.test.js @@ -277,7 +277,7 @@ describeRepeats('resends', () => { const publishedBefore = published.slice() const receivedMsgs = [] - sub.once('resent', onResent.wrap(() => { + sub.on('resent', onResent.wrap(() => { expect(receivedMsgs).toEqual(publishedBefore) })) @@ -290,8 +290,8 @@ describeRepeats('resends', () => { for await (const msg of sub) { receivedMsgs.push(msg.getParsedContent()) if (receivedMsgs.length === published.length) { + await wait() // give resent event a chance to fire onResent.reject(new Error('resent never called')) - await wait() await sub.return() } } @@ -321,13 +321,13 @@ describeRepeats('resends', () => { const req = await client.publish(stream.id, message) // should be realtime published.push(message) publishedRequests.push(req) - await wait(500) const receivedMsgs = await collect(sub, async ({ received }) => { if (received.length === published.length) { await sub.return() } }) + await wait() // give resent event a chance to fire const msgs = receivedMsgs expect(msgs).toHaveLength(published.length) expect(msgs).toEqual(published) From 6bb2ae1c6f1324d79a72279eccf03a1ad9a67bda Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 25 Jan 2021 16:18:51 -0500 Subject: [PATCH 397/517] Remove suspicious waits. --- test/integration/SubscriberResends.test.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/integration/SubscriberResends.test.js b/test/integration/SubscriberResends.test.js index e06b54601..5d90a84fb 100644 --- a/test/integration/SubscriberResends.test.js +++ b/test/integration/SubscriberResends.test.js @@ -383,7 +383,6 @@ describeRepeats('resends', () => { const req = await client.publish(stream.id, message) // should be realtime published.push(message) publishedRequests.push(req) - await wait(500) const receivedMsgs = await collect(sub, async ({ received }) => { if (received.length === published.length) { await sub.return() @@ -414,7 +413,6 @@ describeRepeats('resends', () => { const req = await client.publish(stream.id, message) // should be realtime published.push(message) publishedRequests.push(req) - await wait(500) const received = [] for await (const m of sub) { received.push(m) @@ -475,7 +473,6 @@ describeRepeats('resends', () => { const req = await client.publish(stream.id, message) // should be realtime published.push(message) publishedRequests.push(req) - await wait(500) const END_AFTER = 3 const receivedMsgs = await collect(sub, async ({ received }) => { if (received.length === END_AFTER) { From 302fb6a756ffe4e262c262bb6b24bbf690803313 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 13:29:53 -0500 Subject: [PATCH 398/517] Fix resend event depending on resend stream done vs msg processed ordering. --- src/subscribe/index.js | 72 ++++++++++++++-------- src/subscribe/pipeline.js | 3 +- src/utils/index.js | 4 +- test/integration/SubscriberResends.test.js | 15 +++-- test/utils.js | 4 +- 5 files changed, 64 insertions(+), 34 deletions(-) diff --git a/src/subscribe/index.js b/src/subscribe/index.js index 225dc3159..a41decbe4 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -523,54 +523,76 @@ export default class Subscriber { let resendSubscribeSub - let resentCount + let lastResentMsgId + let lastProcessedMsgId + const resendDone = Defer() + let isResendDone = false + let resentEmitted = false + + function messageIDString(msg) { + return msg.getMessageID().serialize() + } + + function maybeEmitResend() { + if (resentEmitted || !isResendDone) { return } + + // need to account for both cases: + // resent finished after last message got through pipeline + // resent finished before last message got through pipeline + if (!lastResentMsgId || lastProcessedMsgId === lastResentMsgId) { + lastResentMsgId = undefined + resentEmitted = true + resendSubscribeSub.emit('resent') + } + } + const it = pipeline([ async function* HandleResends() { - // Inconvience here - // emitting the resent event is a bit tricky in this setup because the subscription - // doesn't know anything about the source of the messages - // can't emit resent immediately after resent stream end since - // the message is not yet through the message pipeline - // - // Solution is to count number of resent messages - // and emit resent once subscription has seen that many messages - let count = 0 - for await (const msg of resendSubscribeSub.resend) { - count += 1 - yield msg - } - - resentCount = count - if (resentCount === 0) { - // no resent - resendSubscribeSub.emit('resent') + try { + // Inconvience here + // emitting the resent event is a bit tricky in this setup because the subscription + // doesn't know anything about the source of the messages + // can't emit resent immediately after resent stream end since + // the message is not yet through the message pipeline + let currentMsgId + try { + for await (const msg of resendSubscribeSub.resend) { + currentMsgId = messageIDString(msg.streamMessage) + yield msg + } + } finally { + lastResentMsgId = currentMsgId + } + } finally { + isResendDone = true + maybeEmitResend() + resendDone.resolve() } }, async function* ResendThenRealtime(src) { yield* src + await resendDone // ensure realtime doesn't start until resend ends yield* resendSubscribeSub.realtime }, ], end) - let msgCount = 0 const resendTask = resendMessageStream.subscribe() const realtimeTask = this.subscribe({ ...options, + msgStream: it, afterSteps: [ async function* detectEndOfResend(src) { for await (const msg of src) { + const id = messageIDString(msg) try { - msgCount += 1 yield msg } finally { - if (resentCount && msgCount === resentCount) { - resendSubscribeSub.emit('resent') - } + lastProcessedMsgId = id + maybeEmitResend() } } }, ], - msgStream: it, }, onMessage) // eslint-disable-next-line semi-style diff --git a/src/subscribe/pipeline.js b/src/subscribe/pipeline.js index ebdaa2dd0..2dfbe141f 100644 --- a/src/subscribe/pipeline.js +++ b/src/subscribe/pipeline.js @@ -28,7 +28,7 @@ async function collect(src) { export default function MessagePipeline(client, opts = {}, onFinally = () => {}) { const options = validateOptions(opts) - const { key, afterSteps = [], onError = (err) => { throw err } } = options + const { key, afterSteps = [], beforeSteps = [], onError = (err) => { throw err } } = options const id = counterId('MessagePipeline') + key /* eslint-disable object-curly-newline */ @@ -43,6 +43,7 @@ export default function MessagePipeline(client, opts = {}, onFinally = () => {}) const p = pipeline([ // take messages msgStream, + ...beforeSteps, // unpack stream message async function* getStreamMessage(src) { for await (const { streamMessage } of src) { diff --git a/src/utils/index.js b/src/utils/index.js index 7299acc16..c1c41292a 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -216,11 +216,11 @@ export function Defer(executor = () => {}) { }) function wrap(fn) { - return async (...args) => Promise.resolve(fn(...args)).then(resolve, reject) + return async (...args) => Promise.resolve().then(async () => fn(...args)).then(resolve, reject) } function wrapError(fn) { - return async (...args) => Promise.resolve(fn(...args)).catch(reject) + return async (...args) => Promise.resolve().then(async () => fn(...args)).catch(reject) } function handleErrBack(err) { diff --git a/test/integration/SubscriberResends.test.js b/test/integration/SubscriberResends.test.js index 5d90a84fb..acda54340 100644 --- a/test/integration/SubscriberResends.test.js +++ b/test/integration/SubscriberResends.test.js @@ -175,6 +175,9 @@ describeRepeats('resends', () => { describe('with resend data', () => { beforeAll(async () => { await client.connect() + }) + + beforeAll(async () => { const results = await publishTestMessages.raw(MAX_MESSAGES, { waitForLast: true, }) @@ -286,17 +289,21 @@ describeRepeats('resends', () => { const req = await client.publish(stream.id, newMessage) // should be realtime published.push(newMessage) publishedRequests.push(req) - + let t for await (const msg of sub) { receivedMsgs.push(msg.getParsedContent()) if (receivedMsgs.length === published.length) { - await wait() // give resent event a chance to fire - onResent.reject(new Error('resent never called')) await sub.return() + clearTimeout(t) + t = setTimeout(() => { + // await wait() // give resent event a chance to fire + onResent.reject(new Error('resent never called')) + }, 250) } } await onResent + clearTimeout(t) expect(receivedMsgs).toHaveLength(published.length) expect(receivedMsgs).toEqual(published) @@ -327,7 +334,6 @@ describeRepeats('resends', () => { } }) - await wait() // give resent event a chance to fire const msgs = receivedMsgs expect(msgs).toHaveLength(published.length) expect(msgs).toEqual(published) @@ -356,7 +362,6 @@ describeRepeats('resends', () => { for await (const msg of sub) { receivedMsgs.push(msg.getParsedContent()) if (receivedMsgs.length === published.length) { - await wait() await sub.return() } } diff --git a/test/utils.js b/test/utils.js index 68fa3f0ec..c764cc53f 100644 --- a/test/utils.js +++ b/test/utils.js @@ -147,6 +147,8 @@ export function getPublishTestMessages(client, defaultOpts = {}) { waitForLastTimeout, beforeEach = (m) => m, afterEach = () => {}, + timestamp, + partitionKey, createMessage = Msg, } = validateOptions({ ...defaultOpts, @@ -162,7 +164,7 @@ export function getPublishTestMessages(client, defaultOpts = {}) { const request = await pTimeout(client.publish({ streamId, streamPartition, - }, message), timeout, `publish timeout ${streamId}: ${i} ${inspect(message)}`) + }, message, timestamp, partitionKey), timeout, `publish timeout ${streamId}: ${i} ${inspect(message)}`) published.push([ message, request, From 634d237d85ea3332a6625be5a991fc5430766e45 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 13:38:56 -0500 Subject: [PATCH 399/517] Add TEST_REPEATS=5 to CI. --- .github/workflows/nodejs.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 6a97ff029..04de2b3f9 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -55,6 +55,8 @@ jobs: strategy: matrix: node-version: [12.x, 14.x] + env: + TEST_REPEATS: 5 steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 @@ -63,7 +65,7 @@ jobs: - name: npm ci run: npm ci - name: test-unit - timeout-minutes: 2 + timeout-minutes: 7 run: npm run test-unit integration: @@ -93,6 +95,7 @@ jobs: env: TEST_NAME: ${{ matrix.test-name }} WEBSOCKET_URL: ${{ matrix.websocket-url.url}} + TEST_REPEATS: 5 steps: - uses: actions/checkout@v2 From 6f266e10a808282e851c0e21f0a42addae914b08 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 13:43:55 -0500 Subject: [PATCH 400/517] Restore previous Defer wrap functions async timing. --- src/utils/index.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/utils/index.js b/src/utils/index.js index c1c41292a..f0cff05e9 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -216,11 +216,25 @@ export function Defer(executor = () => {}) { }) function wrap(fn) { - return async (...args) => Promise.resolve().then(async () => fn(...args)).then(resolve, reject) + return async (...args) => { + try { + return resolve(await fn(...args)) + } catch (err) { + reject(err) + } + return Promise.resolve() + } } function wrapError(fn) { - return async (...args) => Promise.resolve().then(async () => fn(...args)).catch(reject) + return async (...args) => { + try { + return await fn(...args) + } catch (err) { + reject(err) + } + return Promise.resolve() + } } function handleErrBack(err) { From ff53fb32d8b5aa12a8b2df26b6f580a555116391 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 14:12:15 -0500 Subject: [PATCH 401/517] Tidy package.json. --- package.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index f2411952d..b4c967a3e 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "main": "dist/streamr-client.nodejs.js", "browser": "dist/streamr-client.web.min.js", "directories": { + "example": "examples", "test": "test" }, "scripts": { @@ -113,5 +114,9 @@ "optionalDependencies": { "bufferutil": "^4.0.2", "utf-8-validate": "^5.0.3" - } + }, + "bugs": { + "url": "https://github.com/streamr-dev/streamr-client/issues" + }, + "homepage": "https://github.com/streamr-dev/streamr-client#readme" } From 35688ee3c8b338266a1383d4aa01168d50cd2add Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 14:22:36 -0500 Subject: [PATCH 402/517] Increase stress test timeout. --- test/flakey/EnvStressTest.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/flakey/EnvStressTest.test.js b/test/flakey/EnvStressTest.test.js index 71751e081..f8e5d54f8 100644 --- a/test/flakey/EnvStressTest.test.js +++ b/test/flakey/EnvStressTest.test.js @@ -5,7 +5,7 @@ import config from '../integration/config' const TEST_REPEATS = 6 const MAX_CONCURRENCY = 24 -const TEST_TIMEOUT = 2000 +const TEST_TIMEOUT = 5000 const INC_FACTOR = 1.5 /* eslint-disable require-atomic-updates, no-loop-func */ From 8fcf620e0000bdc4042ed8a8fb3156efaadc55af Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 15:27:41 -0500 Subject: [PATCH 403/517] Ensure gapfill messages run through message pipeline. Moved ordering before validation & decryption. --- src/stream/KeyExchange.js | 5 +++-- src/subscribe/OrderMessages.js | 3 ++- src/subscribe/pipeline.js | 30 ++++++++++++++++-------------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/stream/KeyExchange.js b/src/stream/KeyExchange.js index b95f64c6b..159c5ea6e 100644 --- a/src/stream/KeyExchange.js +++ b/src/stream/KeyExchange.js @@ -479,16 +479,17 @@ export function SubscriberKeyExchange(client, { groupKeys = {} } = {}) { }) groupKeyIds.forEach((id) => { if (!pending.has(id)) { return } + const groupKey = groupKeyStore.get(id) const task = pending.get(id) - task.resolve(groupKeyStore.get(id)) pending.delete(id) + task.resolve(groupKey) }) } catch (err) { groupKeyIds.forEach((id) => { if (!pending.has(id)) { return } const task = pending.get(id) - task.reject(err) pending.delete(id) + task.reject(err) }) } } diff --git a/src/subscribe/OrderMessages.js b/src/subscribe/OrderMessages.js index d51ea604d..424a68bc4 100644 --- a/src/subscribe/OrderMessages.js +++ b/src/subscribe/OrderMessages.js @@ -38,8 +38,9 @@ export default function OrderMessages(client, options = {}) { client.debug('gap %o', { streamId, streamPartition, publisherId, msgChainId, from, to, }) + // eslint-disable-next-line no-use-before-define - const resendMessageStream = await resendStream(client, { + const resendMessageStream = resendStream(client, { streamId, streamPartition, from, to, publisherId, msgChainId, }) diff --git a/src/subscribe/pipeline.js b/src/subscribe/pipeline.js index 2dfbe141f..76c12ff33 100644 --- a/src/subscribe/pipeline.js +++ b/src/subscribe/pipeline.js @@ -43,6 +43,7 @@ export default function MessagePipeline(client, opts = {}, onFinally = () => {}) const p = pipeline([ // take messages msgStream, + // custom pipeline steps ...beforeSteps, // unpack stream message async function* getStreamMessage(src) { @@ -50,24 +51,16 @@ export default function MessagePipeline(client, opts = {}, onFinally = () => {}) yield streamMessage } }, + // order messages + orderingUtil, // validate async function* Validate(src) { for await (const streamMessage of src) { - try { - await validate(streamMessage) - } catch (err) { - if (err instanceof ValidationError) { - orderingUtil.markMessageExplicitly(streamMessage) - await onError(err) - // eslint-disable-next-line no-continue - } else { - throw err - } - } - + await validate(streamMessage) yield streamMessage } }, + // decrypt decrypt, // parse content async function* Parse(src) { @@ -81,8 +74,17 @@ export default function MessagePipeline(client, opts = {}, onFinally = () => {}) yield streamMessage } }, - // order messages - orderingUtil, + // special handling for bye message + async function* ByeMessageSpecialHandling(src) { + for await (const orderedMessage of src) { + if (orderedMessage.isByeMessage()) { + yield orderedMessage + break + } else { + yield orderedMessage + } + } + }, // custom pipeline steps ...afterSteps ], async (err, ...args) => { From 0090b3f28b6411e289b15c87fa2f6df203fe4f7f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 15:45:03 -0500 Subject: [PATCH 404/517] Clean up unused. --- src/subscribe/pipeline.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/subscribe/pipeline.js b/src/subscribe/pipeline.js index 76c12ff33..acb83b64e 100644 --- a/src/subscribe/pipeline.js +++ b/src/subscribe/pipeline.js @@ -1,5 +1,3 @@ -import { Errors } from 'streamr-client-protocol' - import { counterId } from '../utils' import { pipeline } from '../utils/iterators' import { validateOptions } from '../stream/utils' @@ -9,8 +7,6 @@ import messageStream from './messageStream' import OrderMessages from './OrderMessages' import Decrypt from './Decrypt' -const { ValidationError } = Errors - export { SignatureRequiredError } from './Validator' async function collect(src) { @@ -68,7 +64,6 @@ export default function MessagePipeline(client, opts = {}, onFinally = () => {}) try { streamMessage.getParsedContent() } catch (err) { - orderingUtil.markMessageExplicitly(streamMessage) await onError(err) } yield streamMessage From 3e5802e9b2249d00ac62f1f59a7208af4df8a4da Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 15:45:32 -0500 Subject: [PATCH 405/517] Increase number of messages in testing tool CI tests. --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 04de2b3f9..1579bdf3c 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -212,7 +212,7 @@ jobs: - js-realtime-only - java-realtime-only env: - NUM_MESSAGES: 10 + NUM_MESSAGES: 20 TEST_NAME: ${{ matrix.test-name }} CONFIG_NAME: ${{ matrix.config-name }} steps: From c112c42f17ee05b435e8e04420ebe717716c15a3 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 15:49:32 -0500 Subject: [PATCH 406/517] Linting. --- src/rest/DataUnionEndpoints.js | 8 ++++++-- test/browser/browser.js | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/rest/DataUnionEndpoints.js b/src/rest/DataUnionEndpoints.js index fd0e666a4..ea1b28e8f 100644 --- a/src/rest/DataUnionEndpoints.js +++ b/src/rest/DataUnionEndpoints.js @@ -930,12 +930,16 @@ export async function getMemberBalance(memberAddress, options) { /** * Get token balance for given address * @param {EthereumAddress} address - * @param options such as tokenAddress. If not given, then first check if dataUnion was given in StreamrClient constructor, then check if tokenAddress was given in StreamrClient constructor. + * @param options such as tokenAddress. If not given, then first check if + * dataUnion was given in StreamrClient constructor, then check if tokenAddress + * was given in StreamrClient constructor. * @returns {Promise} token balance in "wei" (10^-18 parts) */ export async function getTokenBalance(address, options) { const a = parseAddress(this, address) - const tokenAddressMainnet = options.tokenAddress || await getMainnetContractReadOnly(this, options).then((c) => c.token()).catch(() => null) || this.options.tokenAddress + const tokenAddressMainnet = options.tokenAddress || ( + await getMainnetContractReadOnly(this, options).then((c) => c.token()).catch(() => null) || this.options.tokenAddress + ) if (!tokenAddressMainnet) { throw new Error('tokenAddress option not found') } const provider = this.ethereum.getMainnetProvider() const token = new Contract(tokenAddressMainnet, [{ diff --git a/test/browser/browser.js b/test/browser/browser.js index c6f247278..1695f3a9e 100644 --- a/test/browser/browser.js +++ b/test/browser/browser.js @@ -9,6 +9,7 @@ describe('StreamrClient', () => { const url = process.env.WEBSOCKET_URL ? `&WEBSOCKET_URL=${encodeURIComponent(process.env.WEBSOCKET_URL)}` : '' const restUrl = process.env.REST_URL ? `&REST_URL=${encodeURIComponent(process.env.REST_URL)}` : '' const browserUrl = `http://localhost:8880?streamName=${streamName}${url}${restUrl}` + // eslint-disable-next-line no-console console.info(browserUrl) return browser.url(browserUrl) }) From 4d0b88940c3c06975333a9854756acff0edf3178 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 16:01:07 -0500 Subject: [PATCH 407/517] Fix validator error handling. --- src/subscribe/OrderMessages.js | 7 +------ src/subscribe/pipeline.js | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/subscribe/OrderMessages.js b/src/subscribe/OrderMessages.js index 424a68bc4..abe127f0b 100644 --- a/src/subscribe/OrderMessages.js +++ b/src/subscribe/OrderMessages.js @@ -27,12 +27,7 @@ export default function OrderMessages(client, options = {}) { return } - // end stream or push into queue. - if (orderedMessage.isByeMessage()) { - outStream.end(orderedMessage) - } else { - outStream.push(orderedMessage) - } + outStream.push(orderedMessage) }, async (from, to, publisherId, msgChainId) => { if (done) { return } client.debug('gap %o', { diff --git a/src/subscribe/pipeline.js b/src/subscribe/pipeline.js index acb83b64e..0f9eb9042 100644 --- a/src/subscribe/pipeline.js +++ b/src/subscribe/pipeline.js @@ -52,8 +52,12 @@ export default function MessagePipeline(client, opts = {}, onFinally = () => {}) // validate async function* Validate(src) { for await (const streamMessage of src) { - await validate(streamMessage) - yield streamMessage + try { + await validate(streamMessage) + yield streamMessage + } catch (err) { + await onError(err) + } } }, // decrypt @@ -63,20 +67,22 @@ export default function MessagePipeline(client, opts = {}, onFinally = () => {}) for await (const streamMessage of src) { try { streamMessage.getParsedContent() + yield streamMessage } catch (err) { await onError(err) } - yield streamMessage } }, // special handling for bye message async function* ByeMessageSpecialHandling(src) { for await (const orderedMessage of src) { - if (orderedMessage.isByeMessage()) { - yield orderedMessage - break - } else { - yield orderedMessage + yield orderedMessage + try { + if (orderedMessage.isByeMessage()) { + break + } + } catch (err) { + await onError(err) } } }, From a32db2ed912c23e5a27d38632c06c53b0c553db3 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 16:23:38 -0500 Subject: [PATCH 408/517] Order messages once to get gaps, then again at end of pipeline. --- src/subscribe/pipeline.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/subscribe/pipeline.js b/src/subscribe/pipeline.js index 0f9eb9042..dd4c64288 100644 --- a/src/subscribe/pipeline.js +++ b/src/subscribe/pipeline.js @@ -47,7 +47,7 @@ export default function MessagePipeline(client, opts = {}, onFinally = () => {}) yield streamMessage } }, - // order messages + // order messages (fill gaps) orderingUtil, // validate async function* Validate(src) { @@ -73,11 +73,13 @@ export default function MessagePipeline(client, opts = {}, onFinally = () => {}) } } }, + // re-order messages + OrderMessages(client, options), // special handling for bye message async function* ByeMessageSpecialHandling(src) { for await (const orderedMessage of src) { - yield orderedMessage try { + yield orderedMessage if (orderedMessage.isByeMessage()) { break } From edd3545c58c950b0b983508b7170e25f8cf63e8a Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 16:26:20 -0500 Subject: [PATCH 409/517] Run ordering twice, first to get gapfills then again to re-order messages after async validation/decryption. --- src/subscribe/OrderMessages.js | 8 ++++++-- src/subscribe/pipeline.js | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/subscribe/OrderMessages.js b/src/subscribe/OrderMessages.js index abe127f0b..fb141e809 100644 --- a/src/subscribe/OrderMessages.js +++ b/src/subscribe/OrderMessages.js @@ -14,7 +14,7 @@ const { OrderingUtil } = Utils */ export default function OrderMessages(client, options = {}) { - const { gapFillTimeout, retryResendAfter } = client.options + const { gapFillTimeout, retryResendAfter, gapFill = true } = client.options const { streamId, streamPartition } = validateOptions(options) const outStream = new PushQueue() // output buffer @@ -29,7 +29,7 @@ export default function OrderMessages(client, options = {}) { outStream.push(orderedMessage) }, async (from, to, publisherId, msgChainId) => { - if (done) { return } + if (done || !gapFill) { return } client.debug('gap %o', { streamId, streamPartition, publisherId, msgChainId, from, to, }) @@ -61,6 +61,10 @@ export default function OrderMessages(client, options = {}) { // eslint-disable-next-line require-yield async function* WriteToOrderingUtil(src) { for await (const msg of src) { + if (!gapFill) { + orderingUtil.markMessageExplicitly(msg) + } + orderingUtil.add(msg) // note no yield // orderingUtil writes to outStream itself diff --git a/src/subscribe/pipeline.js b/src/subscribe/pipeline.js index dd4c64288..30e4f14c0 100644 --- a/src/subscribe/pipeline.js +++ b/src/subscribe/pipeline.js @@ -73,8 +73,11 @@ export default function MessagePipeline(client, opts = {}, onFinally = () => {}) } } }, - // re-order messages - OrderMessages(client, options), + // re-order messages (ignore gaps) + OrderMessages(client, { + ...options, + gapFill: false, + }), // special handling for bye message async function* ByeMessageSpecialHandling(src) { for await (const orderedMessage of src) { From 493f8a3f3e2daa73946c202b58b1713f9f00020a Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 16:38:36 -0500 Subject: [PATCH 410/517] Fix pipeline error/ordering handling. --- src/subscribe/pipeline.js | 19 ++++++++++++------- test/integration/Validation.test.js | 2 +- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/subscribe/pipeline.js b/src/subscribe/pipeline.js index 30e4f14c0..577c50d5e 100644 --- a/src/subscribe/pipeline.js +++ b/src/subscribe/pipeline.js @@ -36,6 +36,12 @@ export default function MessagePipeline(client, opts = {}, onFinally = () => {}) } = options /* eslint-enable object-curly-newline */ + // re-order messages (ignore gaps) + const internalOrderingUtil = OrderMessages(client, { + ...options, + gapFill: false, + }) + const p = pipeline([ // take messages msgStream, @@ -54,10 +60,11 @@ export default function MessagePipeline(client, opts = {}, onFinally = () => {}) for await (const streamMessage of src) { try { await validate(streamMessage) - yield streamMessage } catch (err) { + internalOrderingUtil.markMessageExplicitly(streamMessage) await onError(err) } + yield streamMessage } }, // decrypt @@ -67,22 +74,20 @@ export default function MessagePipeline(client, opts = {}, onFinally = () => {}) for await (const streamMessage of src) { try { streamMessage.getParsedContent() - yield streamMessage } catch (err) { + internalOrderingUtil.markMessageExplicitly(streamMessage) await onError(err) } + yield streamMessage } }, // re-order messages (ignore gaps) - OrderMessages(client, { - ...options, - gapFill: false, - }), + internalOrderingUtil, // special handling for bye message async function* ByeMessageSpecialHandling(src) { for await (const orderedMessage of src) { + yield orderedMessage try { - yield orderedMessage if (orderedMessage.isByeMessage()) { break } diff --git a/test/integration/Validation.test.js b/test/integration/Validation.test.js index f1454da05..6196ee235 100644 --- a/test/integration/Validation.test.js +++ b/test/integration/Validation.test.js @@ -8,7 +8,7 @@ import config from './config' const MAX_MESSAGES = 10 -describeRepeats('GapFill', () => { +describeRepeats('Validation', () => { let expectErrors = 0 // check no errors by default let publishTestMessages let onError = jest.fn() From b4b1fb1e860e5cd1266a7e1c949a454b9f87c229 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 16:56:15 -0500 Subject: [PATCH 411/517] Split npm ci & npm link steps in testing-tool CI. --- .github/workflows/nodejs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 1579bdf3c..0db62b74f 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -229,8 +229,10 @@ jobs: uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.2 with: services-to-start: "mysql redis engine-and-editor cassandra parity-node0 parity-sidechain-node0 bridge broker-node-storage-1 nginx smtp" + - name: npm ci + run: npm ci - name: npm link - run: npm ci && npm link + run: npm link - uses: actions/checkout@v2 with: repository: streamr-dev/streamr-client-testing From a0709f83cea88b7bc6d2c0f572b271a3e82fbda3 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 16:57:29 -0500 Subject: [PATCH 412/517] Reduce TEST_REPEATS from 5 to 2 for integration tests in CI. --- .github/workflows/nodejs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 0db62b74f..cda454975 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -95,7 +95,7 @@ jobs: env: TEST_NAME: ${{ matrix.test-name }} WEBSOCKET_URL: ${{ matrix.websocket-url.url}} - TEST_REPEATS: 5 + TEST_REPEATS: 2 steps: - uses: actions/checkout@v2 From b589499bc230622aa8e3bb2951875ad3b849bb6a Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 26 Jan 2021 21:40:57 -0500 Subject: [PATCH 413/517] Release v5.0.0-beta.5 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 14db63af2..0f0dc2330 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.4", + "version": "5.0.0-beta.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index b4c967a3e..2af2fce92 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.4", + "version": "5.0.0-beta.5", "description": "JavaScript client library for Streamr", "repository": { "type": "git", From f695e9c8c448141efedeb1b05d3b49b35fe83e85 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 27 Jan 2021 09:05:45 -0500 Subject: [PATCH 414/517] Refresh dependencies. --- package-lock.json | 25 ++++++++++++++++------ package.json | 54 +++++++++++++++++++++++------------------------ 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0f0dc2330..e81d09b10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4528,9 +4528,9 @@ } }, "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "requires": { "ms": "2.1.2" } @@ -12343,14 +12343,14 @@ "dev": true }, "sinon": { - "version": "9.2.3", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.3.tgz", - "integrity": "sha512-m+DyAWvqVHZtjnjX/nuShasykFeiZ+nPuEfD4G3gpvKGkXRhkF/6NSt2qN2FjZhfrcHXFzUzI+NLnk+42fnLEw==", + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz", + "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==", "dev": true, "requires": { "@sinonjs/commons": "^1.8.1", "@sinonjs/fake-timers": "^6.0.1", - "@sinonjs/samsam": "^5.3.0", + "@sinonjs/samsam": "^5.3.1", "diff": "^4.0.2", "nise": "^4.0.4", "supports-color": "^7.1.0" @@ -13121,6 +13121,17 @@ "requires": { "debug": "4.3.1", "is2": "^2.0.6" + }, + "dependencies": { + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + } } }, "terminal-link": { diff --git a/package.json b/package.json index 2af2fce92..866c4814e 100644 --- a/package.json +++ b/package.json @@ -42,16 +42,16 @@ "author": "Streamr", "license": "Apache-2.0", "devDependencies": { - "@babel/cli": "^7.12.7", - "@babel/core": "^7.12.7", + "@babel/cli": "^7.12.10", + "@babel/core": "^7.12.10", "@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/plugin-transform-classes": "^7.12.1", "@babel/plugin-transform-modules-commonjs": "^7.12.1", - "@babel/plugin-transform-runtime": "^7.12.1", - "@babel/preset-env": "^7.12.7", - "async-mutex": "^0.2.4", + "@babel/plugin-transform-runtime": "^7.12.10", + "@babel/preset-env": "^7.12.11", + "async-mutex": "^0.2.6", "babel-eslint": "^10.1.0", - "babel-loader": "^8.2.1", + "babel-loader": "^8.2.2", "babel-plugin-add-module-exports": "^1.0.4", "babel-plugin-transform-class-properties": "^6.24.1", "benchmark": "^2.1.4", @@ -64,34 +64,34 @@ "eslint-loader": "^4.0.2", "eslint-plugin-import": "^2.22.1", "eslint-plugin-promise": "^4.2.1", - "ethers": "^5.0.21", + "ethers": "^5.0.26", "express": "^4.17.1", - "geckodriver": "^1.21.0", + "geckodriver": "^1.21.1", "git-revision-webpack-plugin": "^3.0.6", "jest": "^26.6.3", "jest-circus": "^26.6.3", "nightwatch": "^1.5.1", - "sinon": "^9.2.1", - "streamr-test-utils": "^1.1.0", + "sinon": "^9.2.4", + "streamr-test-utils": "^1.1.1", "terser-webpack-plugin": "^4.2.3", - "webpack": "^4.44.1", + "webpack": "^4.44.2", "webpack-bundle-analyzer": "^4.2.0", "webpack-cli": "^4.2.0", - "webpack-merge": "^5.4.0" + "webpack-merge": "^5.4.1" }, "#IMPORTANT": "babel-runtime must be in dependencies, not devDependencies", "dependencies": { "@babel/runtime": "^7.12.5", - "@ethersproject/address": "^5.0.3", - "@ethersproject/bignumber": "^5.0.6", - "@ethersproject/bytes": "^5.0.3", - "@ethersproject/contracts": "^5.0.3", - "@ethersproject/keccak256": "^5.0.6", - "@ethersproject/providers": "^5.0.14", - "@ethersproject/sha2": "^5.0.3", - "@ethersproject/transactions": "^5.0.4", - "@ethersproject/wallet": "^5.0.6", - "debug": "^4.3.1", + "@ethersproject/address": "^5.0.9", + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/contracts": "^5.0.9", + "@ethersproject/keccak256": "^5.0.7", + "@ethersproject/providers": "^5.0.19", + "@ethersproject/sha2": "^5.0.7", + "@ethersproject/transactions": "^5.0.9", + "@ethersproject/wallet": "^5.0.10", + "debug": "^4.3.2", "eventemitter3": "^4.0.7", "lodash.uniqueid": "^4.0.1", "mem": "^8.0.0", @@ -103,17 +103,17 @@ "p-memoize": "^4.0.1", "p-queue": "^6.6.2", "promise-memoize": "^1.2.1", - "qs": "^6.9.4", + "qs": "^6.9.6", "quick-lru": "^5.1.1", "readable-stream": "^3.6.0", "streamr-client-protocol": "^7.1.2", - "uuid": "^8.3.1", + "uuid": "^8.3.2", "webpack-node-externals": "^2.5.2", - "ws": "^7.4.0" + "ws": "^7.4.2" }, "optionalDependencies": { - "bufferutil": "^4.0.2", - "utf-8-validate": "^5.0.3" + "bufferutil": "^4.0.3", + "utf-8-validate": "^5.0.4" }, "bugs": { "url": "https://github.com/streamr-dev/streamr-client/issues" From 5eaf97dd87442ee347f20240d9c98e748a3381ba Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 27 Jan 2021 11:13:53 -0500 Subject: [PATCH 415/517] Refresh dependencies. --- package-lock.json | 68 +++++++++++++++++++++++------------------------ package.json | 18 ++++++------- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/package-lock.json b/package-lock.json index e81d09b10..d2d93bd37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2575,6 +2575,12 @@ "@xtuc/long": "4.2.2" } }, + "@webpack-cli/configtest": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.0.tgz", + "integrity": "sha512-Un0SdBoN1h4ACnIO7EiCjWuyhNI0Jl96JC+63q6xi4HDUYRZn8Auluea9D+v9NWKc5J4sICVEltdBaVjLX39xw==", + "dev": true + }, "@webpack-cli/info": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.1.tgz", @@ -2585,9 +2591,9 @@ } }, "@webpack-cli/serve": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.1.tgz", - "integrity": "sha512-Zj1z6AyS+vqV6Hfi7ngCjFGdHV5EwZNIHo6QfFTNe9PyW+zBU1zJ9BiOW1pmUEq950RC4+Dym6flyA/61/vhyw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.2.tgz", + "integrity": "sha512-03GkWxcgFfm8+WIwcsqJb9agrSDNDDoxaNnexPnCCexP5SCE4IgFd9lNpSy+K2nFqVMpgTFw6SwbmVAVTndVew==", "dev": true }, "@xtuc/ieee754": { @@ -4363,9 +4369,9 @@ "dev": true }, "core-js": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.2.tgz", - "integrity": "sha512-FfApuSRgrR6G5s58casCBd9M2k+4ikuu4wbW6pJyYU7bd9zvFc9qf7vr5xmrZOhT9nn+8uwlH1oRR9jTnFoA3A==" + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.3.tgz", + "integrity": "sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q==" }, "core-js-compat": { "version": "3.8.2", @@ -6595,9 +6601,9 @@ "dev": true }, "geckodriver": { - "version": "1.21.1", - "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-1.21.1.tgz", - "integrity": "sha512-i5pYkYFEjTMkSCWh8agNFJPCUxVPr9I3JsRQ+bAypt73urXFnB73GQxDbJPKzELUeLhbQybhNKNlKjxfgS1yAA==", + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-1.22.1.tgz", + "integrity": "sha512-pT5Wf3AVszsvu0I8XWQT6VX7GzVYtASxzluVMlMXb3wb+jlmE0IFNQ7VGfjpdDrwF/MraukmdFtVQLpSJH0M2A==", "dev": true, "requires": { "adm-zip": "0.4.16", @@ -12898,9 +12904,9 @@ } }, "streamr-test-utils": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/streamr-test-utils/-/streamr-test-utils-1.2.1.tgz", - "integrity": "sha512-rQYAxqZ+LNuPlbZSUV7yuu0innb5BeERvEHsyRuxaYIGRyj0qq6u896IyNeH3Lgdk1A1fGUf6OCgW4/EFnJA2g==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/streamr-test-utils/-/streamr-test-utils-1.3.0.tgz", + "integrity": "sha512-mSjtHIUyoVksv6OXVDMj5BecbEXCKfXRmSmiHav1kJLEHj4oE9cF1N0Ml1I3KL3ARw51+RloRnPtRSexg71m/Q==", "dev": true }, "string-length": { @@ -14117,9 +14123,9 @@ } }, "webpack-bundle-analyzer": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.3.0.tgz", - "integrity": "sha512-J3TPm54bPARx6QG8z4cKBszahnUglcv70+N+8gUqv2I5KOFHJbzBiLx+pAp606so0X004fxM7hqRu10MLjJifA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.0.tgz", + "integrity": "sha512-9DhNa+aXpqdHk8LkLPTBU/dMfl84Y+WE2+KnfI6rSpNRNVKa0VGLjPd2pjFubDeqnWmulFggxmWBxhfJXZnR0g==", "dev": true, "requires": { "acorn": "^8.0.4", @@ -14134,15 +14140,15 @@ }, "dependencies": { "acorn": { - "version": "8.0.4", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.4.tgz", - "integrity": "sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ==", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.5.tgz", + "integrity": "sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg==", "dev": true }, "acorn-walk": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.1.tgz", - "integrity": "sha512-zn/7dYtoTVkG4EoMU55QlQU4F+m+T7Kren6Vj3C2DapWPnakG/DL9Ns5aPAPW5Ixd3uxXrV/BoMKKVFIazPcdg==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.0.2.tgz", + "integrity": "sha512-+bpA9MJsHdZ4bgfDcpk0ozQyhhVct7rzOmO0s1IIr0AGGgKBljss8n2zp11rRP2wid5VGeh04CgeKzgat5/25A==", "dev": true }, "ansi-styles": { @@ -14203,14 +14209,15 @@ } }, "webpack-cli": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.3.1.tgz", - "integrity": "sha512-/F4+9QNZM/qKzzL9/06Am8NXIkGV+/NqQ62Dx7DSqudxxpAgBqYn6V7+zp+0Y7JuWksKUbczRY3wMTd+7Uj6OA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.4.0.tgz", + "integrity": "sha512-/Qh07CXfXEkMu5S8wEpjuaw2Zj/CC0hf/qbTDp6N8N7JjdGuaOjZ7kttz+zhuJO/J5m7alQEhNk9lsc4rC6xgQ==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.0.0", "@webpack-cli/info": "^1.2.1", - "@webpack-cli/serve": "^1.2.1", + "@webpack-cli/serve": "^1.2.2", "colorette": "^1.2.1", "commander": "^6.2.0", "enquirer": "^2.3.6", @@ -14220,7 +14227,7 @@ "interpret": "^2.2.0", "rechoir": "^0.7.0", "v8-compile-cache": "^2.2.0", - "webpack-merge": "^4.2.2" + "webpack-merge": "^5.7.3" }, "dependencies": { "commander": { @@ -14272,15 +14279,6 @@ "requires": { "path-key": "^3.0.0" } - }, - "webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", - "dev": true, - "requires": { - "lodash": "^4.17.15" - } } } }, diff --git a/package.json b/package.json index 866c4814e..6d4891860 100644 --- a/package.json +++ b/package.json @@ -57,8 +57,8 @@ "benchmark": "^2.1.4", "buffer": "^6.0.3", "chromedriver": "^86.0.0", - "core-js": "^3.7.0", - "eslint": "^7.14.0", + "core-js": "^3.8.3", + "eslint": "^7.18.0", "eslint-config-airbnb": "^18.2.1", "eslint-config-streamr-nodejs": "^1.3.0", "eslint-loader": "^4.0.2", @@ -66,18 +66,18 @@ "eslint-plugin-promise": "^4.2.1", "ethers": "^5.0.26", "express": "^4.17.1", - "geckodriver": "^1.21.1", + "geckodriver": "^1.22.1", "git-revision-webpack-plugin": "^3.0.6", "jest": "^26.6.3", "jest-circus": "^26.6.3", "nightwatch": "^1.5.1", "sinon": "^9.2.4", - "streamr-test-utils": "^1.1.1", + "streamr-test-utils": "^1.3.0", "terser-webpack-plugin": "^4.2.3", - "webpack": "^4.44.2", - "webpack-bundle-analyzer": "^4.2.0", - "webpack-cli": "^4.2.0", - "webpack-merge": "^5.4.1" + "webpack": "^4.46.0", + "webpack-bundle-analyzer": "^4.4.0", + "webpack-cli": "^4.4.0", + "webpack-merge": "^5.7.3" }, "#IMPORTANT": "babel-runtime must be in dependencies, not devDependencies", "dependencies": { @@ -99,7 +99,7 @@ "node-fetch": "^2.6.1", "node-webcrypto-ossl": "^2.1.2", "once": "^1.4.0", - "p-limit": "^3.0.2", + "p-limit": "^3.1.0", "p-memoize": "^4.0.1", "p-queue": "^6.6.2", "promise-memoize": "^1.2.1", From d27010040fd8e4e81028e4bdb3d0009ac93edd82 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Wed, 10 Feb 2021 13:10:32 +0200 Subject: [PATCH 416/517] NET-178: Storage node methods #197 Methods to query+modify storage nodes. Two new entity classes: StreamPart (streamId+partition) and StorageNode (currently just an address, but can be extended later). --- src/rest/StreamEndpoints.js | 10 +++++++ src/stream/StorageNode.js | 9 +++++++ src/stream/StreamPart.js | 22 ++++++++++++++++ src/stream/index.js | 33 ++++++++++++++++++++++++ test/integration/StreamEndpoints.test.js | 26 +++++++++++++++++++ 5 files changed, 100 insertions(+) create mode 100644 src/stream/StorageNode.js create mode 100644 src/stream/StreamPart.js diff --git a/src/rest/StreamEndpoints.js b/src/rest/StreamEndpoints.js index 4bfc8a768..923b8e2e3 100644 --- a/src/rest/StreamEndpoints.js +++ b/src/rest/StreamEndpoints.js @@ -7,6 +7,7 @@ import debugFactory from 'debug' import { getEndpointUrl } from '../utils' import { validateOptions } from '../stream/utils' import Stream from '../stream' +import StreamPart from '../stream/StreamPart' import { isKeyExchangeStream } from '../stream/KeyExchange' import authFetch from './authFetch' @@ -202,6 +203,15 @@ export async function getStreamLast(streamObjectOrId) { return json } +export async function getStreamPartsByStorageNode(address) { + const json = await authFetch(getEndpointUrl(this.options.restUrl, 'storageNodes', address, 'streams'), this.session) + let result = [] + json.forEach((stream) => { + result = result.concat(StreamPart.fromStream(stream)) + }) + return result +} + export async function publishHttp(streamObjectOrId, data, requestOptions = {}, keepAlive = true) { let streamId if (streamObjectOrId instanceof Stream) { diff --git a/src/stream/StorageNode.js b/src/stream/StorageNode.js new file mode 100644 index 000000000..604787303 --- /dev/null +++ b/src/stream/StorageNode.js @@ -0,0 +1,9 @@ +export default class StorageNode { + constructor(address) { + this._address = address + } + + getAddress() { + return this._address + } +} diff --git a/src/stream/StreamPart.js b/src/stream/StreamPart.js new file mode 100644 index 000000000..e3a3eab4d --- /dev/null +++ b/src/stream/StreamPart.js @@ -0,0 +1,22 @@ +export default class StreamPart { + constructor(streamId, streamPartition) { + this._streamId = streamId + this._streamPartition = streamPartition + } + + static fromStream({ id, partitions }) { + const result = [] + for (let i = 0; i < partitions; i++) { + result.push(new StreamPart(id, i)) + } + return result + } + + getStreamId() { + return this._streamId + } + + getStreamPartition() { + return this._streamPartition + } +} diff --git a/src/stream/index.js b/src/stream/index.js index e337c5b71..cd1f1d658 100644 --- a/src/stream/index.js +++ b/src/stream/index.js @@ -1,6 +1,8 @@ import { getEndpointUrl } from '../utils' import authFetch from '../rest/authFetch' +import StorageNode from './StorageNode' + export default class Stream { constructor(client, props) { this._client = client @@ -109,6 +111,37 @@ export default class Stream { ) } + async addToStorageNode(address) { + return authFetch( + getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'storageNodes'), + this._client.session, + { + method: 'POST', + body: JSON.stringify({ + address + }) + }, + ) + } + + async removeFromStorageNode(address) { + return authFetch( + getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'storageNodes', address), + this._client.session, + { + method: 'DELETE' + }, + ) + } + + async getStorageNodes() { + const json = await authFetch( + getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'storageNodes'), + this._client.session, + ) + return json.map((item) => new StorageNode(item.storageNodeAddress)) + } + async publish(...theArgs) { return this._client.publish(this.id, ...theArgs) } diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.js index ca5b11da7..e1be6c464 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.js @@ -229,6 +229,32 @@ function TestStreamEndpoints(getName) { expect(await client.getStream(createdStream.id)).toBe(undefined) }) }) + + describe.only('Storage node assignment', () => { + it('add', async () => { + const storageNodeAddress = ethers.Wallet.createRandom().address + const stream = await client.createStream() + await stream.addToStorageNode(storageNodeAddress) + const storageNodes = await stream.getStorageNodes() + expect(storageNodes.length).toBe(1) + expect(storageNodes[0].getAddress()).toBe(storageNodeAddress) + const storedStreamParts = await client.getStreamPartsByStorageNode(storageNodeAddress) + expect(storedStreamParts.length).toBe(1) + expect(storedStreamParts[0].getStreamId()).toBe(stream.id) + expect(storedStreamParts[0].getStreamPartition()).toBe(0) + }) + + it('remove', async () => { + const storageNodeAddress = ethers.Wallet.createRandom().address + const stream = await client.createStream() + await stream.addToStorageNode(storageNodeAddress) + await stream.removeFromStorageNode(storageNodeAddress) + const storageNodes = await stream.getStorageNodes() + expect(storageNodes).toHaveLength(0) + const storedStreamParts = await client.getStreamPartsByStorageNode(storageNodeAddress) + expect(storedStreamParts).toHaveLength(0) + }) + }) } describe('StreamEndpoints', () => { From 61f65419926ecee1f7c6b4da6f56b14bccfa578c Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 1 Feb 2021 15:11:04 -0500 Subject: [PATCH 417/517] Update chromedriver to Chrome 88 --- package-lock.json | 111 ++++++++++++++++------------------------------ package.json | 2 +- 2 files changed, 38 insertions(+), 75 deletions(-) diff --git a/package-lock.json b/package-lock.json index d2d93bd37..e30124484 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2290,16 +2290,6 @@ "@babel/types": "^7.3.0" } }, - "@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", - "dev": true, - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, "@types/graceful-fs": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", @@ -2345,12 +2335,6 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, - "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "dev": true - }, "@types/node": { "version": "14.14.21", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.21.tgz", @@ -2970,12 +2954,12 @@ "dev": true }, "axios": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", - "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", "dev": true, "requires": { - "follow-redirects": "1.5.10" + "follow-redirects": "^1.10.0" } }, "babel-code-frame": { @@ -4007,17 +3991,18 @@ } }, "chromedriver": { - "version": "86.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-86.0.0.tgz", - "integrity": "sha512-byLJWhAfuYOmzRYPDf4asJgGDbI4gJGHa+i8dnQZGuv+6WW1nW1Fg+8zbBMOfLvGn7sKL41kVdmCEVpQHn9oyg==", + "version": "88.0.0", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-88.0.0.tgz", + "integrity": "sha512-EE8rXh7mikxk3VWKjUsz0KCUX8d3HkQ4HgMNJhWrWjzju12dKPPVHO9MY+YaAI5ryXrXGNf0Y4HcNKgW36P/CA==", "dev": true, "requires": { "@testim/chrome-version": "^1.0.7", - "axios": "^0.19.2", - "del": "^5.1.0", + "axios": "^0.21.1", + "del": "^6.0.0", "extract-zip": "^2.0.1", "https-proxy-agent": "^5.0.0", "mkdirp": "^1.0.4", + "proxy-from-env": "^1.1.0", "tcp-port-used": "^1.0.1" } }, @@ -4673,18 +4658,18 @@ } }, "del": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/del/-/del-5.1.0.tgz", - "integrity": "sha512-wH9xOVHnczo9jN2IW68BabcecVPxacIA3g/7z6vhSU/4stOKQzeCRK0yD0A24WiAAUJmmVpWqrERcTxnLo3AnA==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-6.0.0.tgz", + "integrity": "sha512-1shh9DQ23L16oXSZKB2JxpL7iMy2E0S9d517ptA1P8iw0alkPtQcrKH7ru31rYtKwF499HkTu+DRzq3TCKDFRQ==", "dev": true, "requires": { - "globby": "^10.0.1", - "graceful-fs": "^4.2.2", + "globby": "^11.0.1", + "graceful-fs": "^4.2.4", "is-glob": "^4.0.1", "is-path-cwd": "^2.2.0", - "is-path-inside": "^3.0.1", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", + "is-path-inside": "^3.0.2", + "p-map": "^4.0.0", + "rimraf": "^3.0.2", "slash": "^3.0.0" }, "dependencies": { @@ -6142,9 +6127,9 @@ "dev": true }, "fastq": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.0.tgz", - "integrity": "sha512-NL2Qc5L3iQEsyYzweq7qfgy5OtXCmGzGvhElGEd/SoFWEMOEczNh5s5ocaF01HDetxz+p8ecjNPA6cZxxIHmzA==", + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.10.1.tgz", + "integrity": "sha512-AWuv6Ery3pM+dY7LYS8YIaCiQvUaos9OB1RyNgaOWnaX+Tik7Onvcsf8x8c+YtDeT0maYLniBip2hox5KtEXXA==", "dev": true, "requires": { "reusify": "^1.0.4" @@ -6358,30 +6343,10 @@ } }, "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "dev": true, - "requires": { - "debug": "=3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz", + "integrity": "sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA==", + "dev": true }, "for-in": { "version": "1.0.2", @@ -6777,18 +6742,16 @@ "dev": true }, "globby": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", - "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", + "version": "11.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.2.tgz", + "integrity": "sha512-2ZThXDvvV8fYFRVIxnrMQBipZQDr7MxKAmQK1vujaj9/7eF0efG7BPUKJ7jP7G5SLF37xKDXvO4S/KKLj/Z0og==", "dev": true, "requires": { - "@types/glob": "^7.1.1", "array-union": "^2.1.0", "dir-glob": "^3.0.1", - "fast-glob": "^3.0.3", - "glob": "^7.1.3", - "ignore": "^5.1.1", - "merge2": "^1.2.3", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", "slash": "^3.0.0" }, "dependencies": { @@ -7215,9 +7178,9 @@ "dev": true }, "ip-regex": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.2.0.tgz", - "integrity": "sha512-n5cDDeTWWRwK1EBoWwRti+8nP4NbytBBY0pldmnIkq6Z55KNFmWofh4rl9dPZpj+U/nVq7gweR3ylrvMt4YZ5A==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-4.3.0.tgz", + "integrity": "sha512-B9ZWJxHHOHUhUjCPrMpLD4xEq35bUTClHM1S6CBU5ixQnkZmwipwgc96vAd7AAGM9TGHvJR+Uss+/Ak6UphK+Q==", "dev": true }, "ipaddr.js": { @@ -10914,9 +10877,9 @@ } }, "p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, "requires": { "aggregate-error": "^3.0.0" diff --git a/package.json b/package.json index 6d4891860..c0c389530 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "babel-plugin-transform-class-properties": "^6.24.1", "benchmark": "^2.1.4", "buffer": "^6.0.3", - "chromedriver": "^86.0.0", + "chromedriver": "^88.0.0", "core-js": "^3.8.3", "eslint": "^7.18.0", "eslint-config-airbnb": "^18.2.1", From 2c0d9bf2dea8470f84b8def49b5dd45e7aedaa13 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 10 Feb 2021 11:07:37 -0500 Subject: [PATCH 418/517] Fix apikey auth. --- src/user/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/user/index.js b/src/user/index.js index 4f0679cdb..925141ca0 100644 --- a/src/user/index.js +++ b/src/user/index.js @@ -42,7 +42,7 @@ export async function getUserId(client) { const username = await getUsername(client) if (username != null) { - const hexString = hexlify(Buffer.from(await this.getUsername(), 'utf8')) + const hexString = hexlify(Buffer.from(username, 'utf8')) return sha256(hexString) } From 42221ed77d18706f4044bf02969726cbf3babc9f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 10 Feb 2021 13:30:41 -0500 Subject: [PATCH 419/517] Release v5.0.0-beta.6 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e30124484..e8c0787e6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.5", + "version": "5.0.0-beta.6", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c0c389530..97499c96b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.5", + "version": "5.0.0-beta.6", "description": "JavaScript client library for Streamr", "repository": { "type": "git", From 5ea7c5c03ca956831c5a062d2fbe0904d6dfe1e7 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 16 Feb 2021 14:41:18 +0200 Subject: [PATCH 420/517] Basic TypeScript support --- .babel.config.js | 3 +- .babel.node.config.js | 3 +- .eslintrc.js | 8 ++ package-lock.json | 214 ++++++++++++++++++++++++++++++++++++- package.json | 6 +- src/{index.js => index.ts} | 2 +- tsconfig.json | 15 +++ webpack.config.js | 12 +-- 8 files changed, 250 insertions(+), 13 deletions(-) rename src/{index.js => index.ts} (93%) create mode 100644 tsconfig.json diff --git a/.babel.config.js b/.babel.config.js index de6aae5ea..422df09ab 100644 --- a/.babel.config.js +++ b/.babel.config.js @@ -18,7 +18,8 @@ module.exports = { ] }, exclude: ['transform-regenerator', '@babel/plugin-transform-regenerator'] - }] + }], + ['@babel/preset-typescript'] ], plugins: [ "add-module-exports", diff --git a/.babel.node.config.js b/.babel.node.config.js index 6af312e3b..245b492cd 100644 --- a/.babel.node.config.js +++ b/.babel.node.config.js @@ -10,7 +10,8 @@ module.exports = { targets: { node: true } - }] + }], + ['@babel/preset-typescript'] ], plugins: [ 'add-module-exports', diff --git a/.eslintrc.js b/.eslintrc.js index fdfe91128..a69d14f9b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -31,5 +31,13 @@ module.exports = { 'no-restricted-syntax': [ 'error', 'ForInStatement', 'LabeledStatement', 'WithStatement' ], + 'import/extensions': ['error', 'never', { json: 'always' }] + }, + settings: { + 'import/resolver': { + node: { + extensions: ['.js', '.ts'] + } + } } } diff --git a/package-lock.json b/package-lock.json index ce22166ce..d7828847a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -585,6 +585,23 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, + "@babel/plugin-syntax-typescript": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.12.13.tgz", + "integrity": "sha512-cHP3u1JiUiG2LFDKbXnwVad81GvfyIOmCD6HIEId6ojrY0Drfy2q1jw7BwN7dE84+kTnBjLkXoL3IEy/3JPu2w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", + "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", + "dev": true + } + } + }, "@babel/plugin-transform-arrow-functions": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", @@ -890,6 +907,173 @@ "@babel/helper-plugin-utils": "^7.10.4" } }, + "@babel/plugin-transform-typescript": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.13.tgz", + "integrity": "sha512-z1VWskPJxK9tfxoYvePWvzSJC+4pxXr8ArmRm5ofqgi+mwpKg6lvtomkIngBYMJVnKhsFYVysCQLDn//v2RHcg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/plugin-syntax-typescript": "^7.12.13" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/generator": { + "version": "7.12.15", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.15.tgz", + "integrity": "sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.13.tgz", + "integrity": "sha512-Vs/e9wv7rakKYeywsmEBSRC9KtmE7Px+YBlESekLeJOF0zbGUicGfXSNi3o+tfXSNS48U/7K9mIOOCR79Cl3+Q==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-member-expression-to-functions": "^7.12.13", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/helper-replace-supers": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13" + } + }, + "@babel/helper-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.13.tgz", + "integrity": "sha512-B+7nN0gIL8FZ8SvMcF+EPyB21KnCcZHQZFczCxbiNGV/O0rsrSBlWGLzmtBJ3GMjSVMIm4lpFhR+VdVBuIsUcQ==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", + "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", + "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", + "dev": true + }, + "@babel/helper-replace-supers": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.13.tgz", + "integrity": "sha512-pctAOIAMVStI2TMLhozPKbf5yTEXc0OJa0eENheb4w09SrgOWEs+P4nTOZYJQCqs8JlErGLDPDJTiGIp3ygbLg==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.12.13", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/traverse": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/highlight": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", + "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.12.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.15.tgz", + "integrity": "sha512-AQBOU2Z9kWwSZMd6lNjCX0GUgFonL1wAM1db8L8PMk9UDaGsRCArBkU4Sc+UCM3AE4hjbXx+h58Lb3QT4oRmrA==", + "dev": true + }, + "@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/traverse": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz", + "integrity": "sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.12.13", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", + "integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + } + } + }, "@babel/plugin-transform-unicode-escapes": { "version": "7.12.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", @@ -996,6 +1180,25 @@ "esutils": "^2.0.2" } }, + "@babel/preset-typescript": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.12.13.tgz", + "integrity": "sha512-gYry7CeXwD2wtw5qHzrtzKaShEhOfTmKb4i0ZxeYBcBosN5VuAudsNbjX7Oj5EAfQ3K4s4HsVMQRRcqGsPvs2A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-validator-option": "^7.12.11", + "@babel/plugin-transform-typescript": "^7.12.13" + }, + "dependencies": { + "@babel/helper-plugin-utils": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", + "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", + "dev": true + } + } + }, "@babel/runtime": { "version": "7.12.5", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", @@ -1042,9 +1245,9 @@ } }, "@babel/types": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.12.tgz", - "integrity": "sha512-lnIX7piTxOH22xE7fDXDbSHg9MM1/6ORnafpJmov5rs0kX5g4BZxeXNJLXsMRiO0U5Rb8/FvMS6xlTnTHvxonQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", + "integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.12.11", @@ -13464,6 +13667,11 @@ "is-typedarray": "^1.0.0" } }, + "typescript": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.4.tgz", + "integrity": "sha512-+Uru0t8qIRgjuCpiSPpfGuhHecMllk5Zsazj5LZvVsEStEjmIRRBZe+jHjGQvsgS7M1wONy2PQXd67EMyV6acg==" + }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", diff --git a/package.json b/package.json index 97499c96b..53e63e7cd 100644 --- a/package.json +++ b/package.json @@ -8,12 +8,14 @@ }, "main": "dist/streamr-client.nodejs.js", "browser": "dist/streamr-client.web.min.js", + "types": "dist/types/src/index.d.ts", "directories": { "example": "examples", "test": "test" }, "scripts": { - "build": "rm dist/*; NODE_ENV=production webpack --mode=production --progress", + "build": "rm -rf dist; NODE_ENV=production webpack --mode=production --progress && npm run build:types", + "build:types": "tsc --emitDeclarationOnly", "benchmarks": "node test/benchmarks/publish.js && node test/benchmarks/subscribe.js", "prebuild-benchmark": "npm run build -- --config-name=node-lib", "build-benchmark": "npm run benchmarks", @@ -49,6 +51,7 @@ "@babel/plugin-transform-modules-commonjs": "^7.12.1", "@babel/plugin-transform-runtime": "^7.12.10", "@babel/preset-env": "^7.12.11", + "@babel/preset-typescript": "^7.12.13", "async-mutex": "^0.2.6", "babel-eslint": "^10.1.0", "babel-loader": "^8.2.2", @@ -107,6 +110,7 @@ "quick-lru": "^5.1.1", "readable-stream": "^3.6.0", "streamr-client-protocol": "^7.1.2", + "typescript": "^4.1.4", "uuid": "^8.3.2", "webpack-node-externals": "^2.5.2", "ws": "^7.4.2" diff --git a/src/index.js b/src/index.ts similarity index 93% rename from src/index.js rename to src/index.ts index a70115695..d200639c0 100644 --- a/src/index.js +++ b/src/index.ts @@ -7,7 +7,7 @@ import * as DataUnionEndpoints from './rest/DataUnionEndpoints' Object.assign(StreamrClient.prototype, { ...StreamEndpoints, ...LoginEndpoints, - ...DataUnionEndpoints, + ...DataUnionEndpoints }) export default StreamrClient diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 000000000..277e2659b --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "es6", + "module": "commonjs", + "allowJs": true, + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist", + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 6cfa15224..256000d2d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -25,7 +25,7 @@ module.exports = (env, argv) => { const commonConfig = { mode: isProduction ? 'production' : 'development', - entry: path.join(__dirname, 'src', 'index.js'), + entry: path.join(__dirname, 'src', 'index.ts'), devtool: 'source-map', output: { path: path.join(__dirname, 'dist'), @@ -41,7 +41,7 @@ module.exports = (env, argv) => { module: { rules: [ { - test: /(\.jsx|\.js)$/, + test: /(\.jsx|\.js|\.ts)$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', @@ -53,7 +53,7 @@ module.exports = (env, argv) => { } }, { - test: /(\.jsx|\.js)$/, + test: /(\.jsx|\.js|\.ts)$/, loader: 'eslint-loader', exclude: /(node_modules|streamr-client-protocol|dist)/, // excluding streamr-client-protocol makes build work when 'npm link'ed }, @@ -61,7 +61,7 @@ module.exports = (env, argv) => { }, resolve: { modules: [path.resolve('./node_modules'), path.resolve('./src')], - extensions: ['.json', '.js'], + extensions: ['.json', '.js', '.ts'], }, plugins: [ gitRevisionPlugin, @@ -87,7 +87,7 @@ module.exports = (env, argv) => { serverConfig.module.rules = [ { - test: /(\.jsx|\.js)$/, + test: /(\.jsx|\.js|\.ts)$/, exclude: /(node_modules|bower_components)/, use: { loader: 'babel-loader', @@ -99,7 +99,7 @@ module.exports = (env, argv) => { } }, { - test: /(\.jsx|\.js)$/, + test: /(\.jsx|\.js|\.ts)$/, loader: 'eslint-loader', exclude: /(node_modules|streamr-client-protocol|dist)/, // excluding streamr-client-protocol makes build work when 'npm link'ed }, From 8609f2fb31d67d8c26fff01d468705173096d810 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 16 Feb 2021 14:41:24 +0200 Subject: [PATCH 421/517] Fix test file --- test/legacy/HistoricalSubscription.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/legacy/HistoricalSubscription.test.js b/test/legacy/HistoricalSubscription.test.js index 939d5c364..582dc880d 100644 --- a/test/legacy/HistoricalSubscription.test.js +++ b/test/legacy/HistoricalSubscription.test.js @@ -1,4 +1,3 @@ -/ import sinon from 'sinon' import { ControlLayer, MessageLayer, Errors } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' From 488747f81bb5920eba7b9b8d7477865ab744c988 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 16 Feb 2021 14:41:28 +0200 Subject: [PATCH 422/517] Export Subscription class --- src/subscribe/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/subscribe/index.js b/src/subscribe/index.js index a41decbe4..456ec49ef 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -10,7 +10,7 @@ import Validator from './Validator' import messageStream from './messageStream' import resendStream from './resendStream' -class Subscription extends Emitter { +export class Subscription extends Emitter { constructor(client, opts, onFinally = () => {}) { super() this.client = client From e1e7a2b3452baae37bb513be36c89c1d524cb070 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 16 Feb 2021 14:41:34 +0200 Subject: [PATCH 423/517] Add placeholder type annotations --- .eslintrc.js | 6 +- package-lock.json | 12 +++ package.json | 2 + src/{StreamrClient.js => StreamrClient.ts} | 87 ++++++++++----- ...nionEndpoints.js => DataUnionEndpoints.ts} | 102 +++++++++--------- .../{LoginEndpoints.js => LoginEndpoints.ts} | 13 +-- ...{StreamEndpoints.js => StreamEndpoints.ts} | 38 +++---- src/types.ts | 3 + 8 files changed, 161 insertions(+), 102 deletions(-) rename src/{StreamrClient.js => StreamrClient.ts} (80%) rename src/rest/{DataUnionEndpoints.js => DataUnionEndpoints.ts} (92%) rename src/rest/{LoginEndpoints.js => LoginEndpoints.ts} (86%) rename src/rest/{StreamEndpoints.js => StreamEndpoints.ts} (83%) create mode 100644 src/types.ts diff --git a/.eslintrc.js b/.eslintrc.js index a69d14f9b..18ba0b22b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -31,7 +31,11 @@ module.exports = { 'no-restricted-syntax': [ 'error', 'ForInStatement', 'LabeledStatement', 'WithStatement' ], - 'import/extensions': ['error', 'never', { json: 'always' }] + 'import/extensions': ['error', 'never', { json: 'always' }], + 'lines-between-class-members': 'off', + 'padded-blocks': 'off', + 'no-use-before-define': 'off', + 'import/order': 'off' }, settings: { 'import/resolver': { diff --git a/package-lock.json b/package-lock.json index d7828847a..afacd097c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2493,6 +2493,12 @@ "@babel/types": "^7.3.0" } }, + "@types/debug": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", + "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", + "dev": true + }, "@types/graceful-fs": { "version": "4.1.4", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", @@ -2556,6 +2562,12 @@ "integrity": "sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA==", "dev": true }, + "@types/qs": { + "version": "6.9.5", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.5.tgz", + "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==", + "dev": true + }, "@types/stack-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", diff --git a/package.json b/package.json index 53e63e7cd..ddb2cb616 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,8 @@ "@babel/plugin-transform-runtime": "^7.12.10", "@babel/preset-env": "^7.12.11", "@babel/preset-typescript": "^7.12.13", + "@types/debug": "^4.1.5", + "@types/qs": "^6.9.5", "async-mutex": "^0.2.6", "babel-eslint": "^10.1.0", "babel-loader": "^8.2.2", diff --git a/src/StreamrClient.js b/src/StreamrClient.ts similarity index 80% rename from src/StreamrClient.js rename to src/StreamrClient.ts index ac7a898b1..91cae1d94 100644 --- a/src/StreamrClient.js +++ b/src/StreamrClient.ts @@ -1,4 +1,5 @@ import EventEmitter from 'eventemitter3' +// @ts-expect-error import { ControlLayer } from 'streamr-client-protocol' import Debug from 'debug' @@ -11,23 +12,24 @@ import Connection from './Connection' import Publisher from './publish' import Subscriber from './subscribe' import { getUserId } from './user' +import { Todo } from './types' /** * Wrap connection message events with message parsing. */ class StreamrConnection extends Connection { - constructor(...args) { + constructor(...args: Todo) { super(...args) this.on('message', this.onConnectionMessage) } // eslint-disable-next-line class-methods-use-this - parse(messageEvent) { + parse(messageEvent: Todo) { return ControlLayer.ControlMessage.deserialize(messageEvent.data) } - onConnectionMessage(messageEvent) { + onConnectionMessage(messageEvent: Todo) { let controlMessage try { controlMessage = this.parse(messageEvent) @@ -48,28 +50,39 @@ class StreamrConnection extends Connection { } class StreamrCached { - constructor(client) { + + client: Todo + getStream: Todo + getUserInfo: Todo + isStreamPublisher: Todo + isStreamSubscriber: Todo + getUserId: Todo + + constructor(client: StreamrClient) { this.client = client const cacheOptions = client.options.cache + // @ts-expect-error this.getStream = CacheAsyncFn(client.getStream.bind(client), { ...cacheOptions, - cacheKey([maybeStreamId]) { + cacheKey([maybeStreamId]: Todo) { const { streamId } = validateOptions(maybeStreamId) return streamId } }) this.getUserInfo = CacheAsyncFn(client.getUserInfo.bind(client), cacheOptions) + // @ts-expect-error this.isStreamPublisher = CacheAsyncFn(client.isStreamPublisher.bind(client), { ...cacheOptions, - cacheKey([maybeStreamId, ethAddress]) { + cacheKey([maybeStreamId, ethAddress]: Todo) { const { streamId } = validateOptions(maybeStreamId) return `${streamId}|${ethAddress}` } }) + // @ts-expect-error this.isStreamSubscriber = CacheAsyncFn(client.isStreamSubscriber.bind(client), { ...cacheOptions, - cacheKey([maybeStreamId, ethAddress]) { + cacheKey([maybeStreamId, ethAddress]: Todo) { const { streamId } = validateOptions(maybeStreamId) return `${streamId}|${ethAddress}` } @@ -78,10 +91,10 @@ class StreamrCached { this.getUserId = CacheAsyncFn(client.getUserId.bind(client), cacheOptions) } - clearStream(streamId) { + clearStream(streamId: Todo) { this.getStream.clear() - this.isStreamPublisher.clearMatching((s) => s.startsWith(streamId)) - this.isStreamSubscriber.clearMatching((s) => s.startsWith(streamId)) + this.isStreamPublisher.clearMatching((s: Todo) => s.startsWith(streamId)) + this.isStreamSubscriber.clearMatching((s: Todo) => s.startsWith(streamId)) } clearUser() { @@ -91,6 +104,7 @@ class StreamrCached { clear() { this.clearUser() + // @ts-expect-error this.clearStream() } } @@ -99,7 +113,19 @@ class StreamrCached { const uid = process.pid != null ? process.pid : `${uuid().slice(-4)}${uuid().slice(0, 4)}` export default class StreamrClient extends EventEmitter { - constructor(options = {}, connection) { + + id: string + debug: Debug.Debugger + options: Todo + getUserInfo: Todo + session: Session + connection: StreamrConnection + publisher: Todo + subscriber: Subscriber + cached: StreamrCached + ethereum: StreamrEthereum + + constructor(options: Todo = {}, connection?: StreamrConnection) { super() this.id = counterId(`${this.constructor.name}:${uid}`) this.debug = Debug(this.id) @@ -135,6 +161,7 @@ export default class StreamrClient extends EventEmitter { .on('disconnected', this.onConnectionDisconnected) .on('error', this.onConnectionError) + // @ts-expect-error this.publisher = new Publisher(this) this.subscriber = new Subscriber(this) this.cached = new StreamrCached(this) @@ -151,12 +178,12 @@ export default class StreamrClient extends EventEmitter { this.emit('disconnected') } - onConnectionError(err) { + onConnectionError(err: Todo) { this.emit('error', new Connection.ConnectionError(err)) } - getErrorEmitter(source) { - return (err) => { + getErrorEmitter(source: Todo) { + return (err: Todo) => { if (!(err instanceof Connection.ConnectionError || err.reason instanceof Connection.ConnectionError)) { // emit non-connection errors this.emit('error', err) @@ -166,11 +193,12 @@ export default class StreamrClient extends EventEmitter { } } - _onError(err, ...args) { + _onError(err: Todo, ...args: Todo) { + // @ts-expect-error this.onError(err, ...args) } - async send(request) { + async send(request: Todo) { return this.connection.send(request) } @@ -178,7 +206,7 @@ export default class StreamrClient extends EventEmitter { * Override to control output */ - onError(error) { // eslint-disable-line class-methods-use-this + onError(error: Todo) { // eslint-disable-line class-methods-use-this console.error(error) } @@ -214,11 +242,12 @@ export default class StreamrClient extends EventEmitter { ]) } - getSubscriptions(...args) { + getSubscriptions(...args: Todo) { return this.subscriber.getAll(...args) } - getSubscription(...args) { + getSubscription(...args: Todo) { + // @ts-expect-error return this.subscriber.get(...args) } @@ -234,7 +263,7 @@ export default class StreamrClient extends EventEmitter { return this.session.logout() } - async publish(...args) { + async publish(...args: Todo) { return this.publisher.publish(...args) } @@ -242,17 +271,17 @@ export default class StreamrClient extends EventEmitter { return getUserId(this) } - setNextGroupKey(...args) { + setNextGroupKey(...args: Todo) { return this.publisher.setNextGroupKey(...args) } - rotateGroupKey(...args) { + rotateGroupKey(...args: Todo) { return this.publisher.rotateGroupKey(...args) } - async subscribe(opts, onMessage) { - let subTask - let sub + async subscribe(opts: Todo, onMessage: Todo) { + let subTask: Todo + let sub: Todo const hasResend = !!(opts.resend || opts.from || opts.to || opts.last) const onEnd = () => { if (sub && typeof onMessage === 'function') { @@ -281,11 +310,11 @@ export default class StreamrClient extends EventEmitter { return subTask } - async unsubscribe(opts) { + async unsubscribe(opts: Todo) { await this.subscriber.unsubscribe(opts) } - async resend(opts, onMessage) { + async resend(opts: Todo, onMessage: Todo) { const task = this.subscriber.resend(opts) if (typeof onMessage !== 'function') { return task @@ -304,11 +333,11 @@ export default class StreamrClient extends EventEmitter { return task } - enableAutoConnect(...args) { + enableAutoConnect(...args: Todo) { return this.connection.enableAutoConnect(...args) } - enableAutoDisconnect(...args) { + enableAutoDisconnect(...args: Todo) { return this.connection.enableAutoDisconnect(...args) } diff --git a/src/rest/DataUnionEndpoints.js b/src/rest/DataUnionEndpoints.ts similarity index 92% rename from src/rest/DataUnionEndpoints.js rename to src/rest/DataUnionEndpoints.ts index ea1b28e8f..d9f512a45 100644 --- a/src/rest/DataUnionEndpoints.js +++ b/src/rest/DataUnionEndpoints.ts @@ -17,6 +17,7 @@ import { Contract } from '@ethersproject/contracts' import { keccak256 } from '@ethersproject/keccak256' import { verifyMessage } from '@ethersproject/wallet' import debug from 'debug' +import { Todo } from '../types' import { until, getEndpointUrl } from '../utils' @@ -259,7 +260,7 @@ const sidechainAmbABI = [{ /** @typedef {String} EthereumAddress */ -function throwIfBadAddress(address, variableDescription) { +function throwIfBadAddress(address: Todo, variableDescription: Todo) { try { return getAddress(address) } catch (e) { @@ -273,7 +274,7 @@ function throwIfBadAddress(address, variableDescription) { * @param {EthereumAddress} inputAddress from user (NOT case sensitive) * @returns {EthereumAddress} with checksum case */ -function parseAddress(client, inputAddress) { +function parseAddress(client: Todo, inputAddress: Todo) { if (isAddress(inputAddress)) { return getAddress(inputAddress) } @@ -281,8 +282,8 @@ function parseAddress(client, inputAddress) { } // Find the Asyncronous Message-passing Bridge sidechain ("home") contract -let cachedSidechainAmb -async function getSidechainAmb(client, options) { +let cachedSidechainAmb: Todo +async function getSidechainAmb(client: Todo, options: Todo) { if (!cachedSidechainAmb) { const getAmbPromise = async () => { const mainnetProvider = client.ethereum.getMainnetProvider() @@ -306,7 +307,7 @@ async function getSidechainAmb(client, options) { return cachedSidechainAmb } -async function getMainnetAmb(client, options) { +async function getMainnetAmb(client: Todo, options: Todo) { const mainnetProvider = client.ethereum.getMainnetProvider() const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, mainnetProvider) @@ -314,7 +315,7 @@ async function getMainnetAmb(client, options) { return new Contract(mainnetAmbAddress, mainnetAmbABI, mainnetProvider) } -async function requiredSignaturesHaveBeenCollected(client, messageHash, options = {}) { +async function requiredSignaturesHaveBeenCollected(client: Todo, messageHash: Todo, options: Todo = {}) { const sidechainAmb = await getSidechainAmb(client, options) const requiredSignatureCount = await sidechainAmb.requiredSignatures() @@ -329,7 +330,7 @@ async function requiredSignaturesHaveBeenCollected(client, messageHash, options } // move signatures from sidechain to mainnet -async function transportSignatures(client, messageHash, options) { +async function transportSignatures(client: Todo, messageHash: Todo, options: Todo) { const sidechainAmb = await getSidechainAmb(client, options) const message = await sidechainAmb.message(messageHash) const messageId = '0x' + message.substr(2, 64) @@ -339,8 +340,8 @@ async function transportSignatures(client, messageHash, options) { log(`${collectedSignatureCount} signatures reported, getting them from the sidechain AMB...`) const signatures = await Promise.all(Array(collectedSignatureCount).fill(0).map(async (_, i) => sidechainAmb.signature(messageHash, i))) - const [vArray, rArray, sArray] = [[], [], []] - signatures.forEach((signature, i) => { + const [vArray, rArray, sArray]: Todo = [[], [], []] + signatures.forEach((signature: string, i) => { log(` Signature ${i}: ${signature} (len=${signature.length}=${signature.length / 2 - 1} bytes)`) rArray.push(signature.substr(2, 64)) sArray.push(signature.substr(66, 64)) @@ -355,6 +356,7 @@ async function transportSignatures(client, messageHash, options) { let gasLimit try { // magic number suggested by https://github.com/poanetwork/tokenbridge/blob/master/oracle/src/utils/constants.js + // @ts-expect-error gasLimit = await mainnetAmb.estimateGas.executeSignatures(message, packedSignatures) + 200000 log(`Calculated gas limit: ${gasLimit.toString()}`) } catch (e) { @@ -412,7 +414,7 @@ async function transportSignatures(client, messageHash, options) { // template for withdraw functions // client could be replaced with AMB (mainnet and sidechain) -async function untilWithdrawIsComplete(client, getWithdrawTxFunc, getBalanceFunc, options = {}) { +async function untilWithdrawIsComplete(client: Todo, getWithdrawTxFunc: Todo, getBalanceFunc: Todo, options: Todo = {}) { const { pollingIntervalMs = 1000, retryTimeoutMs = 60000, @@ -424,7 +426,7 @@ async function untilWithdrawIsComplete(client, getWithdrawTxFunc, getBalanceFunc if (options.payForSignatureTransport) { log(`Got receipt, filtering UserRequestForSignature from ${tr.events.length} events...`) // event UserRequestForSignature(bytes32 indexed messageId, bytes encodedData); - const sigEventArgsArray = tr.events.filter((e) => e.event === 'UserRequestForSignature').map((e) => e.args) + const sigEventArgsArray = tr.events.filter((e: Todo) => e.event === 'UserRequestForSignature').map((e: Todo) => e.args) if (sigEventArgsArray.length < 1) { throw new Error("No UserRequestForSignature events emitted from withdraw transaction, can't transport withdraw to mainnet") } @@ -464,9 +466,9 @@ async function untilWithdrawIsComplete(client, getWithdrawTxFunc, getBalanceFunc // TODO: calculate addresses in JS instead of asking over RPC, see data-union-solidity/contracts/CloneLib.sol // key the cache with name only, since PROBABLY one StreamrClient will ever use only one private key -const mainnetAddressCache = {} // mapping: "name" -> mainnet address +const mainnetAddressCache: Todo = {} // mapping: "name" -> mainnet address /** @returns {Promise} Mainnet address for Data Union */ -async function getDataUnionMainnetAddress(client, dataUnionName, deployerAddress, options = {}) { +async function getDataUnionMainnetAddress(client: Todo, dataUnionName: Todo, deployerAddress: Todo, options: Todo = {}) { if (!mainnetAddressCache[dataUnionName]) { const provider = client.ethereum.getMainnetProvider() const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress @@ -479,9 +481,9 @@ async function getDataUnionMainnetAddress(client, dataUnionName, deployerAddress } // TODO: calculate addresses in JS -const sidechainAddressCache = {} // mapping: mainnet address -> sidechain address +const sidechainAddressCache: Todo = {} // mapping: mainnet address -> sidechain address /** @returns {Promise} Sidechain address for Data Union */ -async function getDataUnionSidechainAddress(client, duMainnetAddress, options = {}) { +async function getDataUnionSidechainAddress(client: Todo, duMainnetAddress: Todo, options: Todo = {}) { if (!sidechainAddressCache[duMainnetAddress]) { const provider = client.ethereum.getMainnetProvider() const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress @@ -493,7 +495,7 @@ async function getDataUnionSidechainAddress(client, duMainnetAddress, options = return sidechainAddressCache[duMainnetAddress] } -function getMainnetContractReadOnly(client, options = {}) { +function getMainnetContractReadOnly(client: Todo, options: Todo = {}) { let dataUnion = options.dataUnion || options.dataUnionAddress || client.options.dataUnion if (isAddress(dataUnion)) { const provider = client.ethereum.getMainnetProvider() @@ -506,13 +508,13 @@ function getMainnetContractReadOnly(client, options = {}) { return dataUnion } -function getMainnetContract(client, options = {}) { +function getMainnetContract(client: Todo, options: Todo = {}) { const du = getMainnetContractReadOnly(client, options) const signer = client.ethereum.getSigner() return du.connect(signer) } -async function getSidechainContract(client, options = {}) { +async function getSidechainContract(client: Todo, options: Todo = {}) { const signer = await client.ethereum.getSidechainSigner() const duMainnet = getMainnetContractReadOnly(client, options) const duSidechainAddress = await getDataUnionSidechainAddress(client, duMainnet.address, options) @@ -520,7 +522,7 @@ async function getSidechainContract(client, options = {}) { return duSidechain } -async function getSidechainContractReadOnly(client, options = {}) { +async function getSidechainContractReadOnly(client: Todo, options: Todo = {}) { const provider = await client.ethereum.getSidechainProvider() const duMainnet = getMainnetContractReadOnly(client, options) const duSidechainAddress = await getDataUnionSidechainAddress(client, duMainnet.address, options) @@ -532,12 +534,12 @@ async function getSidechainContractReadOnly(client, options = {}) { // admin: DEPLOY AND SETUP DATA UNION // ////////////////////////////////////////////////////////////////// -export async function calculateDataUnionMainnetAddress(dataUnionName, deployerAddress, options) { +export async function calculateDataUnionMainnetAddress(dataUnionName: Todo, deployerAddress: Todo, options: Todo) { const address = getAddress(deployerAddress) // throws if bad address return getDataUnionMainnetAddress(this, dataUnionName, address, options) } -export async function calculateDataUnionSidechainAddress(duMainnetAddress, options) { +export async function calculateDataUnionSidechainAddress(duMainnetAddress: Todo, options: Todo) { const address = getAddress(duMainnetAddress) // throws if bad address return getDataUnionSidechainAddress(this, address, options) } @@ -572,7 +574,7 @@ export async function calculateDataUnionSidechainAddress(duMainnetAddress, optio * @param {DeployOptions} options such as adminFee (default: 0) * @return {Promise} that resolves when the new DU is deployed over the bridge to side-chain */ -export async function deployDataUnion(options = {}) { +export async function deployDataUnion(options: Todo = {}) { const { owner, joinPartAgents, @@ -643,13 +645,16 @@ export async function deployDataUnion(options = {}) { ) const dataUnion = new Contract(duMainnetAddress, dataUnionMainnetABI, mainnetWallet) + // @ts-expect-error dataUnion.deployTxReceipt = tr + // @ts-expect-error dataUnion.sidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, sidechainProvider) return dataUnion } -export async function getDataUnionContract(options = {}) { +export async function getDataUnionContract(options: Todo = {}) { const ret = getMainnetContract(this, options) + // @ts-expect-error ret.sidechain = await getSidechainContract(this, options) return ret } @@ -660,7 +665,7 @@ export async function getDataUnionContract(options = {}) { * @param {String} name describes the secret * @returns {String} the server-generated secret */ -export async function createSecret(dataUnionMainnetAddress, name = 'Untitled Data Union Secret') { +export async function createSecret(dataUnionMainnetAddress: Todo, name: string = 'Untitled Data Union Secret') { const duAddress = getAddress(dataUnionMainnetAddress) // throws if bad address const url = getEndpointUrl(this.options.restUrl, 'dataunions', duAddress, 'secrets') const res = await authFetch( @@ -688,7 +693,7 @@ export async function createSecret(dataUnionMainnetAddress, name = 'Untitled Dat * @param {List} memberAddressList to kick * @returns {Promise} partMembers sidechain transaction */ -export async function kick(memberAddressList, options = {}) { +export async function kick(memberAddressList: Todo, options: Todo = {}) { const members = memberAddressList.map(getAddress) // throws if there are bad addresses const duSidechain = await getSidechainContract(this, options) const tx = await duSidechain.partMembers(members) @@ -701,7 +706,7 @@ export async function kick(memberAddressList, options = {}) { * @param {List} memberAddressList to add * @returns {Promise} addMembers sidechain transaction */ -export async function addMembers(memberAddressList, options = {}) { +export async function addMembers(memberAddressList: Todo, options: Todo = {}) { const members = memberAddressList.map(getAddress) // throws if there are bad addresses const duSidechain = await getSidechainContract(this, options) const tx = await duSidechain.addMembers(members) @@ -716,7 +721,7 @@ export async function addMembers(memberAddressList, options = {}) { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} get receipt once withdraw transaction is confirmed */ -export async function withdrawMember(memberAddress, options) { +export async function withdrawMember(memberAddress: Todo, options: Todo) { const address = getAddress(memberAddress) // throws if bad address const tr = await untilWithdrawIsComplete( this, @@ -734,7 +739,7 @@ export async function withdrawMember(memberAddress, options) { * @param {EthereumOptions} options * @returns {Promise} await on call .wait to actually send the tx */ -export async function getWithdrawMemberTx(memberAddress, options) { +export async function getWithdrawMemberTx(memberAddress: Todo, options: Todo) { const a = getAddress(memberAddress) // throws if bad address const duSidechain = await getSidechainContract(this, options) return duSidechain.withdrawAll(a, true) // sendToMainnet=true @@ -749,7 +754,7 @@ export async function getWithdrawMemberTx(memberAddress, options) { * @param {EthereumOptions} options * @returns {Promise} get receipt once withdraw transaction is confirmed */ -export async function withdrawToSigned(memberAddress, recipientAddress, signature, options) { +export async function withdrawToSigned(memberAddress: Todo, recipientAddress: Todo, signature: Todo, options: Todo) { const from = getAddress(memberAddress) // throws if bad address const to = getAddress(recipientAddress) const tr = await untilWithdrawIsComplete( @@ -770,7 +775,7 @@ export async function withdrawToSigned(memberAddress, recipientAddress, signatur * @param {EthereumOptions} options * @returns {Promise} await on call .wait to actually send the tx */ -export async function getWithdrawToSignedTx(memberAddress, recipientAddress, signature, options) { +export async function getWithdrawToSignedTx(memberAddress: Todo, recipientAddress: Todo, signature: Todo, options: Todo) { const duSidechain = await getSidechainContract(this, options) return duSidechain.withdrawAllToSigned(memberAddress, recipientAddress, true, signature) // sendToMainnet=true } @@ -780,7 +785,7 @@ export async function getWithdrawToSignedTx(memberAddress, recipientAddress, sig * @param {number} newFeeFraction between 0.0 and 1.0 * @param {EthereumOptions} options */ -export async function setAdminFee(newFeeFraction, options) { +export async function setAdminFee(newFeeFraction: Todo, options: Todo) { if (newFeeFraction < 0 || newFeeFraction > 1) { throw new Error('newFeeFraction argument must be a number between 0...1, got: ' + newFeeFraction) } @@ -794,13 +799,13 @@ export async function setAdminFee(newFeeFraction, options) { * Get data union admin fee fraction that admin gets from each revenue event * @returns {number} between 0.0 and 1.0 */ -export async function getAdminFee(options) { +export async function getAdminFee(options: Todo) { const duMainnet = getMainnetContractReadOnly(this, options) const adminFeeBN = await duMainnet.adminFeeFraction() return +adminFeeBN.toString() / 1e18 } -export async function getAdminAddress(options) { +export async function getAdminAddress(options: Todo) { const duMainnet = getMainnetContractReadOnly(this, options) return duMainnet.owner() } @@ -818,7 +823,7 @@ export async function getAdminAddress(options) { * @property {String} member Ethereum mainnet address of the joining member. If not given, use StreamrClient authentication key * @property {String} secret if given, and correct, join the data union immediately */ -export async function joinDataUnion(options = {}) { +export async function joinDataUnion(options: Todo = {}) { const { member, secret, @@ -828,6 +833,7 @@ export async function joinDataUnion(options = {}) { const body = { memberAddress: parseAddress(this, member) } + // @ts-expect-error if (secret) { body.secret = secret } const url = getEndpointUrl(this.options.restUrl, 'dataunions', dataUnion.address, 'joinRequests') @@ -851,7 +857,7 @@ export async function joinDataUnion(options = {}) { * @param {Number} retryTimeoutMs (optional, default: 60000) give up * @return {Promise} resolves when member is in the data union (or fails with HTTP error) */ -export async function hasJoined(memberAddress, options = {}) { +export async function hasJoined(memberAddress: Todo, options: Todo = {}) { const { pollingIntervalMs = 1000, retryTimeoutMs = 60000, @@ -864,14 +870,14 @@ export async function hasJoined(memberAddress, options = {}) { } // TODO: this needs more thought: probably something like getEvents from sidechain? Heavy on RPC? -export async function getMembers(options) { +export async function getMembers(options: Todo) { const duSidechain = await getSidechainContractReadOnly(this, options) throw new Error(`Not implemented for side-chain data union (at ${duSidechain.address})`) // event MemberJoined(address indexed); // event MemberParted(address indexed); } -export async function getDataUnionStats(options) { +export async function getDataUnionStats(options: Todo) { const duSidechain = await getSidechainContractReadOnly(this, options) const [ totalEarnings, @@ -897,7 +903,7 @@ export async function getDataUnionStats(options) { * @param {EthereumAddress} dataUnion to query * @param {EthereumAddress} memberAddress (optional) if not supplied, get the stats of currently logged in StreamrClient (if auth: privateKey) */ -export async function getMemberStats(memberAddress, options) { +export async function getMemberStats(memberAddress: Todo, options: Todo) { const address = parseAddress(this, memberAddress) // TODO: use duSidechain.getMemberStats(address) once it's implemented, to ensure atomic read // (so that memberData is from same block as getEarnings, otherwise withdrawable will be foobar) @@ -921,7 +927,7 @@ export async function getMemberStats(memberAddress, options) { * @param memberAddress whose balance is returned * @return {Promise} */ -export async function getMemberBalance(memberAddress, options) { +export async function getMemberBalance(memberAddress: Todo, options: Todo) { const address = parseAddress(this, memberAddress) const duSidechain = await getSidechainContractReadOnly(this, options) return duSidechain.getWithdrawableEarnings(address) @@ -935,10 +941,10 @@ export async function getMemberBalance(memberAddress, options) { * was given in StreamrClient constructor. * @returns {Promise} token balance in "wei" (10^-18 parts) */ -export async function getTokenBalance(address, options) { +export async function getTokenBalance(address: Todo, options: Todo) { const a = parseAddress(this, address) const tokenAddressMainnet = options.tokenAddress || ( - await getMainnetContractReadOnly(this, options).then((c) => c.token()).catch(() => null) || this.options.tokenAddress + await getMainnetContractReadOnly(this, options).then((c: Todo) => c.token()).catch(() => null) || this.options.tokenAddress ) if (!tokenAddressMainnet) { throw new Error('tokenAddress option not found') } const provider = this.ethereum.getMainnetProvider() @@ -960,7 +966,7 @@ export async function getTokenBalance(address, options) { * @param {EthereumAddress} contractAddress * @returns {number} 1 for old, 2 for current, zero for "not a data union" */ -export async function getDataUnionVersion(contractAddress) { +export async function getDataUnionVersion(contractAddress: Todo) { const a = getAddress(contractAddress) // throws if bad address const provider = this.ethereum.getMainnetProvider() const du = new Contract(a, [{ @@ -987,7 +993,7 @@ export async function getDataUnionVersion(contractAddress) { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) */ -export async function withdraw(options = {}) { +export async function withdraw(options: Todo = {}) { const tr = await untilWithdrawIsComplete( this, this.getWithdrawTx.bind(this), @@ -1002,7 +1008,7 @@ export async function withdraw(options = {}) { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} await on call .wait to actually send the tx */ -export async function getWithdrawTx(options) { +export async function getWithdrawTx(options: Todo) { const signer = await this.ethereum.getSidechainSigner() const address = await signer.getAddress() const duSidechain = await getSidechainContract(this, options) @@ -1025,7 +1031,7 @@ export async function getWithdrawTx(options) { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) */ -export async function withdrawTo(recipientAddress, options = {}) { +export async function withdrawTo(recipientAddress: Todo, options = {}) { const to = getAddress(recipientAddress) // throws if bad address const tr = await untilWithdrawIsComplete( this, @@ -1042,7 +1048,7 @@ export async function withdrawTo(recipientAddress, options = {}) { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} await on call .wait to actually send the tx */ -export async function getWithdrawTxTo(recipientAddress, options) { +export async function getWithdrawTxTo(recipientAddress: Todo, options: Todo) { const signer = await this.ethereum.getSidechainSigner() const address = await signer.getAddress() const duSidechain = await getSidechainContract(this, options) @@ -1067,7 +1073,7 @@ export async function getWithdrawTxTo(recipientAddress, options) { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {string} signature authorizing withdrawing all earnings to given recipientAddress */ -export async function signWithdrawTo(recipientAddress, options) { +export async function signWithdrawTo(recipientAddress: Todo, options: Todo) { return this.signWithdrawAmountTo(recipientAddress, BigNumber.from(0), options) } @@ -1080,7 +1086,7 @@ export async function signWithdrawTo(recipientAddress, options) { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {string} signature authorizing withdrawing all earnings to given recipientAddress */ -export async function signWithdrawAmountTo(recipientAddress, amountTokenWei, options) { +export async function signWithdrawAmountTo(recipientAddress: Todo, amountTokenWei: Todo, options: Todo) { const to = getAddress(recipientAddress) // throws if bad address const signer = this.ethereum.getSigner() // it shouldn't matter if it's mainnet or sidechain signer since key should be the same const address = await signer.getAddress() diff --git a/src/rest/LoginEndpoints.js b/src/rest/LoginEndpoints.ts similarity index 86% rename from src/rest/LoginEndpoints.js rename to src/rest/LoginEndpoints.ts index 2ab9f967e..24f66ecc6 100644 --- a/src/rest/LoginEndpoints.js +++ b/src/rest/LoginEndpoints.ts @@ -1,8 +1,9 @@ +import { Todo } from '../types' import { getEndpointUrl } from '../utils' import authFetch, { AuthFetchError } from './authFetch' -async function getSessionToken(url, props) { +async function getSessionToken(url: Todo, props: Todo) { return authFetch( url, undefined, @@ -16,7 +17,7 @@ async function getSessionToken(url, props) { ) } -export async function getChallenge(address) { +export async function getChallenge(address: Todo) { this.debug('getChallenge %o', { address, }) @@ -30,7 +31,7 @@ export async function getChallenge(address) { ) } -export async function sendChallengeResponse(challenge, signature, address) { +export async function sendChallengeResponse(challenge: Todo, signature: Todo, address: Todo) { this.debug('sendChallengeResponse %o', { challenge, signature, @@ -45,7 +46,7 @@ export async function sendChallengeResponse(challenge, signature, address) { return getSessionToken(url, props) } -export async function loginWithChallengeResponse(signingFunction, address) { +export async function loginWithChallengeResponse(signingFunction: Todo, address: Todo) { this.debug('loginWithChallengeResponse %o', { address, }) @@ -54,7 +55,7 @@ export async function loginWithChallengeResponse(signingFunction, address) { return this.sendChallengeResponse(challenge, signature, address) } -export async function loginWithApiKey(apiKey) { +export async function loginWithApiKey(apiKey: Todo) { this.debug('loginWithApiKey %o', { apiKey, }) @@ -65,7 +66,7 @@ export async function loginWithApiKey(apiKey) { return getSessionToken(url, props) } -export async function loginWithUsernamePassword(username, password) { +export async function loginWithUsernamePassword(username: Todo, password: Todo) { this.debug('loginWithUsernamePassword %o', { username, }) diff --git a/src/rest/StreamEndpoints.js b/src/rest/StreamEndpoints.ts similarity index 83% rename from src/rest/StreamEndpoints.js rename to src/rest/StreamEndpoints.ts index 923b8e2e3..c71320d8d 100644 --- a/src/rest/StreamEndpoints.js +++ b/src/rest/StreamEndpoints.ts @@ -11,6 +11,7 @@ import StreamPart from '../stream/StreamPart' import { isKeyExchangeStream } from '../stream/KeyExchange' import authFetch from './authFetch' +import { Todo } from '../types' const debug = debugFactory('StreamrClient') @@ -24,7 +25,7 @@ const agentByProtocol = { https: new HttpsAgent(agentSettings), } -function getKeepAliveAgentForUrl(url) { +function getKeepAliveAgentForUrl(url: string) { if (url.startsWith('https')) { return agentByProtocol.https } @@ -38,7 +39,7 @@ function getKeepAliveAgentForUrl(url) { // These function are mixed in to StreamrClient.prototype. // In the below functions, 'this' is intended to be the StreamrClient -export async function getStream(streamId) { +export async function getStream(streamId: Todo) { this.debug('getStream %o', { streamId, }) @@ -62,7 +63,7 @@ export async function getStream(streamId) { } } -export async function listStreams(query = {}) { +export async function listStreams(query: Todo = {}) { this.debug('listStreams %o', { query, }) @@ -71,7 +72,7 @@ export async function listStreams(query = {}) { return json ? json.map((stream) => new Stream(this, stream)) : [] } -export async function getStreamByName(name) { +export async function getStreamByName(name: string) { this.debug('getStreamByName %o', { name, }) @@ -82,7 +83,7 @@ export async function getStreamByName(name) { return json[0] ? new Stream(this, json[0]) : undefined } -export async function createStream(props) { +export async function createStream(props: Todo) { this.debug('createStream %o', { props, }) @@ -98,7 +99,7 @@ export async function createStream(props) { return json ? new Stream(this, json) : undefined } -export async function getOrCreateStream(props) { +export async function getOrCreateStream(props: Todo) { this.debug('getOrCreateStream %o', { props, }) @@ -125,16 +126,16 @@ export async function getOrCreateStream(props) { } } -export async function getStreamPublishers(streamId) { +export async function getStreamPublishers(streamId: Todo) { this.debug('getStreamPublishers %o', { streamId, }) const url = getEndpointUrl(this.options.restUrl, 'streams', streamId, 'publishers') const json = await authFetch(url, this.session) - return json.addresses.map((a) => a.toLowerCase()) + return json.addresses.map((a: string) => a.toLowerCase()) } -export async function isStreamPublisher(streamId, ethAddress) { +export async function isStreamPublisher(streamId: Todo, ethAddress: Todo) { this.debug('isStreamPublisher %o', { streamId, ethAddress, @@ -152,16 +153,16 @@ export async function isStreamPublisher(streamId, ethAddress) { } } -export async function getStreamSubscribers(streamId) { +export async function getStreamSubscribers(streamId: Todo) { this.debug('getStreamSubscribers %o', { streamId, }) const url = getEndpointUrl(this.options.restUrl, 'streams', streamId, 'subscribers') const json = await authFetch(url, this.session) - return json.addresses.map((a) => a.toLowerCase()) + return json.addresses.map((a: Todo) => a.toLowerCase()) } -export async function isStreamSubscriber(streamId, ethAddress) { +export async function isStreamSubscriber(streamId: Todo, ethAddress: Todo) { this.debug('isStreamSubscriber %o', { streamId, ethAddress, @@ -178,7 +179,7 @@ export async function isStreamSubscriber(streamId, ethAddress) { } } -export async function getStreamValidationInfo(streamId) { +export async function getStreamValidationInfo(streamId: Todo) { this.debug('getStreamValidationInfo %o', { streamId, }) @@ -187,7 +188,7 @@ export async function getStreamValidationInfo(streamId) { return json } -export async function getStreamLast(streamObjectOrId) { +export async function getStreamLast(streamObjectOrId: Todo) { const { streamId, streamPartition = 0, count = 1 } = validateOptions(streamObjectOrId) this.debug('getStreamLast %o', { streamId, @@ -203,18 +204,19 @@ export async function getStreamLast(streamObjectOrId) { return json } -export async function getStreamPartsByStorageNode(address) { +export async function getStreamPartsByStorageNode(address: Todo) { const json = await authFetch(getEndpointUrl(this.options.restUrl, 'storageNodes', address, 'streams'), this.session) - let result = [] - json.forEach((stream) => { + let result: Todo = [] + json.forEach((stream: Todo) => { result = result.concat(StreamPart.fromStream(stream)) }) return result } -export async function publishHttp(streamObjectOrId, data, requestOptions = {}, keepAlive = true) { +export async function publishHttp(streamObjectOrId: Todo, data: Todo, requestOptions: Todo = {}, keepAlive: Todo = true) { let streamId if (streamObjectOrId instanceof Stream) { + // @ts-expect-error streamId = streamObjectOrId.id } else { streamId = streamObjectOrId diff --git a/src/types.ts b/src/types.ts new file mode 100644 index 000000000..ff7342d4a --- /dev/null +++ b/src/types.ts @@ -0,0 +1,3 @@ +export type Todo = any + +export type StreamrClientAndEndpoints = any \ No newline at end of file From b53e327da6dcab7294f7171fbce4d0f1689cee26 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 16 Feb 2021 14:41:43 +0200 Subject: [PATCH 424/517] Tests to support TypeScript --- jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index 7ec99a872..1086a1106 100644 --- a/jest.config.js +++ b/jest.config.js @@ -163,7 +163,7 @@ module.exports = { // A map from regular expressions to paths to transformers transform: { - '\\.js$': ['babel-jest', { + '\\.(js|ts)$': ['babel-jest', { configFile: path.resolve(__dirname, '.babel.node.config.js'), babelrc: false, }] From bfc7141b176abe9ae88952c3f296a3687ed79586 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 16 Feb 2021 14:41:52 +0200 Subject: [PATCH 425/517] Encapsulate endpoints --- package.json | 2 +- src/StreamrClient.ts | 209 +++- src/index.ts | 13 - src/rest/DataUnionEndpoints.ts | 1081 +++++++++-------- src/rest/LoginEndpoints.ts | 150 +-- src/rest/StreamEndpoints.ts | 353 +++--- src/stream/{index.js => index.ts} | 29 +- test/flakey/EnvStressTest.test.js | 2 +- .../DataUnionEndpoints.test.js | 2 +- .../adminWithdrawMember.test.js | 2 +- .../adminWithdrawSigned.test.js | 2 +- .../DataUnionEndpoints/calculate.test.js | 2 +- .../DataUnionEndpoints/withdraw.test.js | 2 +- .../DataUnionEndpoints/withdrawTo.test.js | 2 +- test/integration/Encryption.test.js | 2 +- test/integration/GapFill.test.js | 2 +- test/integration/LoginEndpoints.test.js | 2 +- test/integration/MultipleClients.test.js | 2 +- test/integration/ResendReconnect.test.js | 2 +- test/integration/Resends.test.js | 2 +- test/integration/Sequencing.test.js | 2 +- test/integration/Session.test.js | 2 +- .../integration/StreamConnectionState.test.js | 2 +- test/integration/StreamEndpoints.test.js | 2 +- test/integration/StreamrClient.test.js | 2 +- test/integration/Subscriber.test.js | 2 +- test/integration/SubscriberResends.test.js | 2 +- test/integration/Subscription.test.js | 2 +- test/integration/Validation.test.js | 2 +- test/integration/authFetch.test.js | 2 +- test/unit/Session.test.js | 2 +- webpack.config.js | 2 +- 32 files changed, 1062 insertions(+), 825 deletions(-) delete mode 100644 src/index.ts rename src/stream/{index.js => index.ts} (84%) diff --git a/package.json b/package.json index ddb2cb616..417f496b5 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "main": "dist/streamr-client.nodejs.js", "browser": "dist/streamr-client.web.min.js", - "types": "dist/types/src/index.d.ts", + "types": "dist/types/src/StreamrClient.d.ts", "directories": { "example": "examples", "test": "test" diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index 91cae1d94..05c4d8424 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -13,6 +13,9 @@ import Publisher from './publish' import Subscriber from './subscribe' import { getUserId } from './user' import { Todo } from './types' +import { StreamEndpoints } from './rest/StreamEndpoints' +import { LoginEndpoints } from './rest/LoginEndpoints' +import { DataUnionEndpoints } from './rest/DataUnionEndpoints' /** * Wrap connection message events with message parsing. @@ -61,7 +64,6 @@ class StreamrCached { constructor(client: StreamrClient) { this.client = client const cacheOptions = client.options.cache - // @ts-expect-error this.getStream = CacheAsyncFn(client.getStream.bind(client), { ...cacheOptions, cacheKey([maybeStreamId]: Todo) { @@ -70,7 +72,6 @@ class StreamrCached { } }) this.getUserInfo = CacheAsyncFn(client.getUserInfo.bind(client), cacheOptions) - // @ts-expect-error this.isStreamPublisher = CacheAsyncFn(client.isStreamPublisher.bind(client), { ...cacheOptions, cacheKey([maybeStreamId, ethAddress]: Todo) { @@ -79,7 +80,6 @@ class StreamrCached { } }) - // @ts-expect-error this.isStreamSubscriber = CacheAsyncFn(client.isStreamSubscriber.bind(client), { ...cacheOptions, cacheKey([maybeStreamId, ethAddress]: Todo) { @@ -117,13 +117,15 @@ export default class StreamrClient extends EventEmitter { id: string debug: Debug.Debugger options: Todo - getUserInfo: Todo session: Session connection: StreamrConnection publisher: Todo subscriber: Subscriber cached: StreamrCached ethereum: StreamrEthereum + streamEndpoints: StreamEndpoints + loginEndpoints: LoginEndpoints + dataUnionEndpoints: DataUnionEndpoints constructor(options: Todo = {}, connection?: StreamrConnection) { super() @@ -144,7 +146,6 @@ export default class StreamrClient extends EventEmitter { }) // bind event handlers - this.getUserInfo = this.getUserInfo.bind(this) this.onConnectionConnected = this.onConnectionConnected.bind(this) this.onConnectionDisconnected = this.onConnectionDisconnected.bind(this) this._onError = this._onError.bind(this) @@ -166,6 +167,10 @@ export default class StreamrClient extends EventEmitter { this.subscriber = new Subscriber(this) this.cached = new StreamrCached(this) this.ethereum = new StreamrEthereum(this) + + this.streamEndpoints = new StreamEndpoints(this) + this.loginEndpoints = new LoginEndpoints(this) + this.dataUnionEndpoints = new DataUnionEndpoints(this) } async onConnectionConnected() { @@ -352,4 +357,198 @@ export default class StreamrClient extends EventEmitter { static generateEthereumAccount() { return StreamrEthereum.generateEthereumAccount() } + + // TODO many of these methods that use streamEndpoints/loginEndpoints/dataUnionEndpoints are private: remove those + + async getStream(streamId: Todo) { + return this.streamEndpoints.getStream(streamId) + } + + async listStreams(query: Todo = {}) { + return this.streamEndpoints.listStreams(query) + } + + async getStreamByName(name: string) { + return this.streamEndpoints.getStreamByName(name) + } + + async createStream(props: Todo) { + return this.streamEndpoints.createStream(props) + } + + async getOrCreateStream(props: Todo) { + return this.streamEndpoints.getOrCreateStream(props) + } + + async getStreamPublishers(streamId: Todo) { + return this.streamEndpoints.getStreamPublishers(streamId) + } + + async isStreamPublisher(streamId: Todo, ethAddress: Todo) { + return this.streamEndpoints.isStreamPublisher(streamId, ethAddress) + } + + async getStreamSubscribers(streamId: Todo) { + return this.streamEndpoints.getStreamSubscribers(streamId) + } + + async isStreamSubscriber(streamId: Todo, ethAddress: Todo) { + return this.streamEndpoints.isStreamSubscriber(streamId, ethAddress) + } + + async getStreamValidationInfo(streamId: Todo) { + return this.streamEndpoints.getStreamValidationInfo(streamId) + } + + async getStreamLast(streamObjectOrId: Todo) { + return this.streamEndpoints.getStreamLast(streamObjectOrId) + } + + async getStreamPartsByStorageNode(address: Todo) { + return this.streamEndpoints.getStreamPartsByStorageNode(address) + } + + async publishHttp(streamObjectOrId: Todo, data: Todo, requestOptions: Todo = {}, keepAlive: Todo = true) { + return this.streamEndpoints.publishHttp(streamObjectOrId, data, requestOptions, keepAlive) + } + + async getChallenge(address: Todo) { + return this.loginEndpoints.getChallenge(address) + } + + async sendChallengeResponse(challenge: Todo, signature: Todo, address: Todo) { + return this.loginEndpoints.sendChallengeResponse(challenge, signature, address) + } + + async loginWithChallengeResponse(signingFunction: Todo, address: Todo) { + return this.loginEndpoints.loginWithChallengeResponse(signingFunction, address) + } + + async loginWithApiKey(apiKey: Todo) { + return this.loginEndpoints.loginWithApiKey(apiKey) + } + + async loginWithUsernamePassword(username: Todo, password: Todo) { + return this.loginEndpoints.loginWithUsernamePassword(username, password) + } + + async getUserInfo() { + return this.loginEndpoints.getUserInfo() + } + + async logoutEndpoint() { + return this.loginEndpoints.logoutEndpoint() + } + + async calculateDataUnionMainnetAddress(dataUnionName: Todo, deployerAddress: Todo, options: Todo) { + return this.dataUnionEndpoints.calculateDataUnionMainnetAddress(dataUnionName, deployerAddress, options) + } + + async calculateDataUnionSidechainAddress(duMainnetAddress: Todo, options: Todo) { + return this.dataUnionEndpoints.calculateDataUnionSidechainAddress(duMainnetAddress, options) + } + + async deployDataUnion(options: Todo = {}) { + return this.dataUnionEndpoints.deployDataUnion(options) + } + + async getDataUnionContract(options: Todo = {}) { + return this.dataUnionEndpoints.getDataUnionContract(options) + } + + async createSecret(dataUnionMainnetAddress: Todo, name: string = 'Untitled Data Union Secret') { + return this.dataUnionEndpoints.createSecret(dataUnionMainnetAddress, name) + } + + async kick(memberAddressList: Todo, options: Todo = {}) { + return this.dataUnionEndpoints.kick(memberAddressList, options) + } + + async addMembers(memberAddressList: Todo, options: Todo = {}) { + return this.dataUnionEndpoints.addMembers(memberAddressList, options) + } + + async withdrawMember(memberAddress: Todo, options: Todo) { + return this.dataUnionEndpoints.withdrawMember(memberAddress, options) + } + + async getWithdrawMemberTx(memberAddress: Todo, options: Todo) { + return this.dataUnionEndpoints.getWithdrawMemberTx(memberAddress, options) + } + + async withdrawToSigned(memberAddress: Todo, recipientAddress: Todo, signature: Todo, options: Todo) { + return this.dataUnionEndpoints.withdrawToSigned(memberAddress, recipientAddress, signature, options) + } + + async getWithdrawToSignedTx(memberAddress: Todo, recipientAddress: Todo, signature: Todo, options: Todo) { + return this.dataUnionEndpoints.getWithdrawToSignedTx(memberAddress, recipientAddress, signature, options) + } + + async setAdminFee(newFeeFraction: Todo, options: Todo) { + return this.dataUnionEndpoints.setAdminFee(newFeeFraction, options) + } + + async getAdminFee(options: Todo) { + return this.dataUnionEndpoints.getAdminFee(options) + } + + async getAdminAddress(options: Todo) { + return this.dataUnionEndpoints.getAdminAddress(options) + } + + async joinDataUnion(options: Todo = {}) { + return this.dataUnionEndpoints.joinDataUnion(options) + } + + async hasJoined(memberAddress: Todo, options: Todo = {}) { + return this.dataUnionEndpoints.hasJoined(memberAddress, options) + } + + async getMembers(options: Todo) { + return this.dataUnionEndpoints.getMembers(options) + } + + async getDataUnionStats(options: Todo) { + return this.dataUnionEndpoints.getDataUnionStats(options) + } + + async getMemberStats(memberAddress: Todo, options: Todo) { + return this.dataUnionEndpoints.getMemberStats(memberAddress, options) + } + + async getMemberBalance(memberAddress: Todo, options: Todo) { + return this.dataUnionEndpoints.getMemberBalance(memberAddress, options) + } + + async getTokenBalance(address: Todo, options: Todo) { + return this.dataUnionEndpoints.getTokenBalance(address, options) + } + + async getDataUnionVersion(contractAddress: Todo) { + return this.dataUnionEndpoints.getDataUnionVersion(contractAddress) + } + + async withdraw(options: Todo = {}) { + return this.dataUnionEndpoints.withdraw(options) + } + + async getWithdrawTx(options: Todo) { + return this.dataUnionEndpoints.getWithdrawTx(options) + } + + async withdrawTo(recipientAddress: Todo, options = {}) { + return this.dataUnionEndpoints.withdrawTo(recipientAddress, options) + } + + async getWithdrawTxTo(recipientAddress: Todo, options: Todo) { + return this.dataUnionEndpoints.getWithdrawTxTo(recipientAddress, options) + } + + async signWithdrawTo(recipientAddress: Todo, options: Todo) { + return this.dataUnionEndpoints.signWithdrawTo(recipientAddress, options) + } + + async signWithdrawAmountTo(recipientAddress: Todo, amountTokenWei: Todo, options: Todo) { + return this.dataUnionEndpoints.signWithdrawAmountTo(recipientAddress, amountTokenWei, options) + } } diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index d200639c0..000000000 --- a/src/index.ts +++ /dev/null @@ -1,13 +0,0 @@ -import StreamrClient from './StreamrClient' -import * as StreamEndpoints from './rest/StreamEndpoints' -import * as LoginEndpoints from './rest/LoginEndpoints' -import * as DataUnionEndpoints from './rest/DataUnionEndpoints' - -// Mixin the rest endpoints to the StreamrClient -Object.assign(StreamrClient.prototype, { - ...StreamEndpoints, - ...LoginEndpoints, - ...DataUnionEndpoints -}) - -export default StreamrClient diff --git a/src/rest/DataUnionEndpoints.ts b/src/rest/DataUnionEndpoints.ts index d9f512a45..7daaf158d 100644 --- a/src/rest/DataUnionEndpoints.ts +++ b/src/rest/DataUnionEndpoints.ts @@ -17,6 +17,7 @@ import { Contract } from '@ethersproject/contracts' import { keccak256 } from '@ethersproject/keccak256' import { verifyMessage } from '@ethersproject/wallet' import debug from 'debug' +import StreamrClient from '../StreamrClient' import { Todo } from '../types' import { until, getEndpointUrl } from '../utils' @@ -274,7 +275,7 @@ function throwIfBadAddress(address: Todo, variableDescription: Todo) { * @param {EthereumAddress} inputAddress from user (NOT case sensitive) * @returns {EthereumAddress} with checksum case */ -function parseAddress(client: Todo, inputAddress: Todo) { +function parseAddress(client: StreamrClient, inputAddress: Todo) { if (isAddress(inputAddress)) { return getAddress(inputAddress) } @@ -283,7 +284,7 @@ function parseAddress(client: Todo, inputAddress: Todo) { // Find the Asyncronous Message-passing Bridge sidechain ("home") contract let cachedSidechainAmb: Todo -async function getSidechainAmb(client: Todo, options: Todo) { +async function getSidechainAmb(client: StreamrClient, options: Todo) { if (!cachedSidechainAmb) { const getAmbPromise = async () => { const mainnetProvider = client.ethereum.getMainnetProvider() @@ -297,8 +298,10 @@ async function getSidechainAmb(client: Todo, options: Todo) { outputs: [{ type: 'address' }], stateMutability: 'view', type: 'function' + // @ts-expect-error }], sidechainProvider) const sidechainAmbAddress = await factorySidechain.amb() + // @ts-expect-error return new Contract(sidechainAmbAddress, sidechainAmbABI, sidechainProvider) } cachedSidechainAmb = getAmbPromise() @@ -307,7 +310,7 @@ async function getSidechainAmb(client: Todo, options: Todo) { return cachedSidechainAmb } -async function getMainnetAmb(client: Todo, options: Todo) { +async function getMainnetAmb(client: StreamrClient, options: Todo) { const mainnetProvider = client.ethereum.getMainnetProvider() const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, mainnetProvider) @@ -315,7 +318,7 @@ async function getMainnetAmb(client: Todo, options: Todo) { return new Contract(mainnetAmbAddress, mainnetAmbABI, mainnetProvider) } -async function requiredSignaturesHaveBeenCollected(client: Todo, messageHash: Todo, options: Todo = {}) { +async function requiredSignaturesHaveBeenCollected(client: StreamrClient, messageHash: Todo, options: Todo = {}) { const sidechainAmb = await getSidechainAmb(client, options) const requiredSignatureCount = await sidechainAmb.requiredSignatures() @@ -330,7 +333,7 @@ async function requiredSignaturesHaveBeenCollected(client: Todo, messageHash: To } // move signatures from sidechain to mainnet -async function transportSignatures(client: Todo, messageHash: Todo, options: Todo) { +async function transportSignatures(client: StreamrClient, messageHash: Todo, options: Todo) { const sidechainAmb = await getSidechainAmb(client, options) const message = await sidechainAmb.message(messageHash) const messageId = '0x' + message.substr(2, 64) @@ -406,7 +409,9 @@ async function transportSignatures(client: Todo, messageHash: Todo, options: Tod } const signer = client.ethereum.getSigner() + // @ts-expect-error log(`Sending message from signer=${await signer.getAddress()}`) + // @ts-expect-error const txAMB = await mainnetAmb.connect(signer).executeSignatures(message, packedSignatures) const trAMB = await txAMB.wait() return trAMB @@ -414,7 +419,7 @@ async function transportSignatures(client: Todo, messageHash: Todo, options: Tod // template for withdraw functions // client could be replaced with AMB (mainnet and sidechain) -async function untilWithdrawIsComplete(client: Todo, getWithdrawTxFunc: Todo, getBalanceFunc: Todo, options: Todo = {}) { +async function untilWithdrawIsComplete(client: StreamrClient, getWithdrawTxFunc: Todo, getBalanceFunc: Todo, options: Todo = {}) { const { pollingIntervalMs = 1000, retryTimeoutMs = 60000, @@ -468,7 +473,7 @@ async function untilWithdrawIsComplete(client: Todo, getWithdrawTxFunc: Todo, ge // key the cache with name only, since PROBABLY one StreamrClient will ever use only one private key const mainnetAddressCache: Todo = {} // mapping: "name" -> mainnet address /** @returns {Promise} Mainnet address for Data Union */ -async function getDataUnionMainnetAddress(client: Todo, dataUnionName: Todo, deployerAddress: Todo, options: Todo = {}) { +async function getDataUnionMainnetAddress(client: StreamrClient, dataUnionName: Todo, deployerAddress: Todo, options: Todo = {}) { if (!mainnetAddressCache[dataUnionName]) { const provider = client.ethereum.getMainnetProvider() const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress @@ -483,7 +488,7 @@ async function getDataUnionMainnetAddress(client: Todo, dataUnionName: Todo, dep // TODO: calculate addresses in JS const sidechainAddressCache: Todo = {} // mapping: mainnet address -> sidechain address /** @returns {Promise} Sidechain address for Data Union */ -async function getDataUnionSidechainAddress(client: Todo, duMainnetAddress: Todo, options: Todo = {}) { +async function getDataUnionSidechainAddress(client: StreamrClient, duMainnetAddress: Todo, options: Todo = {}) { if (!sidechainAddressCache[duMainnetAddress]) { const provider = client.ethereum.getMainnetProvider() const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress @@ -495,7 +500,7 @@ async function getDataUnionSidechainAddress(client: Todo, duMainnetAddress: Todo return sidechainAddressCache[duMainnetAddress] } -function getMainnetContractReadOnly(client: Todo, options: Todo = {}) { +function getMainnetContractReadOnly(client: StreamrClient, options: Todo = {}) { let dataUnion = options.dataUnion || options.dataUnionAddress || client.options.dataUnion if (isAddress(dataUnion)) { const provider = client.ethereum.getMainnetProvider() @@ -508,593 +513,613 @@ function getMainnetContractReadOnly(client: Todo, options: Todo = {}) { return dataUnion } -function getMainnetContract(client: Todo, options: Todo = {}) { +function getMainnetContract(client: StreamrClient, options: Todo = {}) { const du = getMainnetContractReadOnly(client, options) const signer = client.ethereum.getSigner() + // @ts-expect-error return du.connect(signer) } -async function getSidechainContract(client: Todo, options: Todo = {}) { +async function getSidechainContract(client: StreamrClient, options: Todo = {}) { const signer = await client.ethereum.getSidechainSigner() const duMainnet = getMainnetContractReadOnly(client, options) const duSidechainAddress = await getDataUnionSidechainAddress(client, duMainnet.address, options) + // @ts-expect-error const duSidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, signer) return duSidechain } -async function getSidechainContractReadOnly(client: Todo, options: Todo = {}) { +async function getSidechainContractReadOnly(client: StreamrClient, options: Todo = {}) { const provider = await client.ethereum.getSidechainProvider() const duMainnet = getMainnetContractReadOnly(client, options) const duSidechainAddress = await getDataUnionSidechainAddress(client, duMainnet.address, options) + // @ts-expect-error const duSidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, provider) return duSidechain } -// ////////////////////////////////////////////////////////////////// -// admin: DEPLOY AND SETUP DATA UNION -// ////////////////////////////////////////////////////////////////// - -export async function calculateDataUnionMainnetAddress(dataUnionName: Todo, deployerAddress: Todo, options: Todo) { - const address = getAddress(deployerAddress) // throws if bad address - return getDataUnionMainnetAddress(this, dataUnionName, address, options) -} - -export async function calculateDataUnionSidechainAddress(duMainnetAddress: Todo, options: Todo) { - const address = getAddress(duMainnetAddress) // throws if bad address - return getDataUnionSidechainAddress(this, address, options) -} - -/** - * TODO: update this comment - * @typedef {object} EthereumOptions all optional, hence "options" - * @property {Wallet | string} wallet or private key, default is currently logged in StreamrClient (if auth: privateKey) - * @property {string} key private key, alias for String wallet - * @property {string} privateKey, alias for String wallet - * @property {providers.Provider} provider to use in case wallet was a String, or omitted - * @property {number} confirmations, default is 1 - * @property {BigNumber} gasPrice in wei (part of ethers overrides), default is whatever the network recommends (ethers.js default) - * @see https://docs.ethers.io/ethers.js/html/api-contract.html#overrides - */ -/** - * @typedef {object} AdditionalDeployOptions for deployDataUnion - * @property {EthereumAddress} owner new data union owner, defaults to StreamrClient authenticated user - * @property {Array} joinPartAgents defaults to just the owner - * @property {number} adminFee fraction (number between 0...1 where 1 means 100%) - * @property {EthereumAddress} factoryMainnetAddress defaults to StreamrClient options - * @property {string} dataUnionName unique (to the DataUnionFactory) identifier of the new data union, must not exist yet - */ -/** - * @typedef {EthereumOptions & AdditionalDeployOptions} DeployOptions - */ -// TODO: gasPrice to overrides (not needed for browser, but would be useful in node.js) +export class DataUnionEndpoints { -/** - * Create a new DataUnionMainnet contract to mainnet with DataUnionFactoryMainnet - * This triggers DataUnionSidechain contract creation in sidechain, over the bridge (AMB) - * @param {DeployOptions} options such as adminFee (default: 0) - * @return {Promise} that resolves when the new DU is deployed over the bridge to side-chain - */ -export async function deployDataUnion(options: Todo = {}) { - const { - owner, - joinPartAgents, - dataUnionName, - adminFee = 0, - sidechainPollingIntervalMs = 1000, - sidechainRetryTimeoutMs = 600000, - } = options + client: StreamrClient - let duName = dataUnionName - if (!duName) { - duName = `DataUnion-${Date.now()}` // TODO: use uuid - log(`dataUnionName generated: ${duName}`) + constructor(client: StreamrClient) { + this.client = client } - if (adminFee < 0 || adminFee > 1) { throw new Error('options.adminFeeFraction must be a number between 0...1, got: ' + adminFee) } - const adminFeeBN = BigNumber.from((adminFee * 1e18).toFixed()) // last 2...3 decimals are going to be gibberish - - const mainnetProvider = this.ethereum.getMainnetProvider() - const mainnetWallet = this.ethereum.getSigner() - const sidechainProvider = this.ethereum.getSidechainProvider() - - // parseAddress defaults to authenticated user (also if "owner" is not an address) - const ownerAddress = parseAddress(this, owner) - - let agentAddressList - if (Array.isArray(joinPartAgents)) { - // getAddress throws if there's an invalid address in the array - agentAddressList = joinPartAgents.map(getAddress) - } else { - // streamrNode needs to be joinPartAgent so that EE join with secret works (and join approvals from Marketplace UI) - agentAddressList = [ownerAddress] - if (this.options.streamrNodeAddress) { - agentAddressList.push(getAddress(this.options.streamrNodeAddress)) - } - } - - const duMainnetAddress = await getDataUnionMainnetAddress(this, duName, ownerAddress, options) - const duSidechainAddress = await getDataUnionSidechainAddress(this, duMainnetAddress, options) + // ////////////////////////////////////////////////////////////////// + // admin: DEPLOY AND SETUP DATA UNION + // ////////////////////////////////////////////////////////////////// - if (await mainnetProvider.getCode(duMainnetAddress) !== '0x') { - throw new Error(`Mainnet data union "${duName}" contract ${duMainnetAddress} already exists!`) + async calculateDataUnionMainnetAddress(dataUnionName: Todo, deployerAddress: Todo, options: Todo) { + const address = getAddress(deployerAddress) // throws if bad address + return getDataUnionMainnetAddress(this.client, dataUnionName, address, options) } - const factoryMainnetAddress = throwIfBadAddress( - options.factoryMainnetAddress || this.options.factoryMainnetAddress, - 'StreamrClient.options.factoryMainnetAddress' - ) - if (await mainnetProvider.getCode(factoryMainnetAddress) === '0x') { - throw new Error(`Data union factory contract not found at ${factoryMainnetAddress}, check StreamrClient.options.factoryMainnetAddress!`) + async calculateDataUnionSidechainAddress(duMainnetAddress: Todo, options: Todo) { + const address = getAddress(duMainnetAddress) // throws if bad address + return getDataUnionSidechainAddress(this.client, address, options) } - // function deployNewDataUnion(address owner, uint256 adminFeeFraction, address[] agents, string duName) - const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, mainnetWallet) - const tx = await factoryMainnet.deployNewDataUnion( - ownerAddress, - adminFeeBN, - agentAddressList, - duName, - ) - const tr = await tx.wait() + /** + * TODO: update this comment + * @typedef {object} EthereumOptions all optional, hence "options" + * @property {Wallet | string} wallet or private key, default is currently logged in StreamrClient (if auth: privateKey) + * @property {string} key private key, alias for String wallet + * @property {string} privateKey, alias for String wallet + * @property {providers.Provider} provider to use in case wallet was a String, or omitted + * @property {number} confirmations, default is 1 + * @property {BigNumber} gasPrice in wei (part of ethers overrides), default is whatever the network recommends (ethers.js default) + * @see https://docs.ethers.io/ethers.js/html/api-contract.html#overrides + */ + /** + * @typedef {object} AdditionalDeployOptions for deployDataUnion + * @property {EthereumAddress} owner new data union owner, defaults to StreamrClient authenticated user + * @property {Array} joinPartAgents defaults to just the owner + * @property {number} adminFee fraction (number between 0...1 where 1 means 100%) + * @property {EthereumAddress} factoryMainnetAddress defaults to StreamrClient options + * @property {string} dataUnionName unique (to the DataUnionFactory) identifier of the new data union, must not exist yet + */ + /** + * @typedef {EthereumOptions & AdditionalDeployOptions} DeployOptions + */ + // TODO: gasPrice to overrides (not needed for browser, but would be useful in node.js) + + /** + * Create a new DataUnionMainnet contract to mainnet with DataUnionFactoryMainnet + * This triggers DataUnionSidechain contract creation in sidechain, over the bridge (AMB) + * @param {DeployOptions} options such as adminFee (default: 0) + * @return {Promise} that resolves when the new DU is deployed over the bridge to side-chain + */ + async deployDataUnion(options: Todo = {}) { + const { + owner, + joinPartAgents, + dataUnionName, + adminFee = 0, + sidechainPollingIntervalMs = 1000, + sidechainRetryTimeoutMs = 600000, + } = options + + let duName = dataUnionName + if (!duName) { + duName = `DataUnion-${Date.now()}` // TODO: use uuid + log(`dataUnionName generated: ${duName}`) + } - log(`Data Union "${duName}" (mainnet: ${duMainnetAddress}, sidechain: ${duSidechainAddress}) deployed to mainnet, waiting for side-chain...`) - await until( - async () => await sidechainProvider.getCode(duSidechainAddress) !== '0x', - sidechainRetryTimeoutMs, - sidechainPollingIntervalMs - ) + if (adminFee < 0 || adminFee > 1) { throw new Error('options.adminFeeFraction must be a number between 0...1, got: ' + adminFee) } + const adminFeeBN = BigNumber.from((adminFee * 1e18).toFixed()) // last 2...3 decimals are going to be gibberish + + const mainnetProvider = this.client.ethereum.getMainnetProvider() + const mainnetWallet = this.client.ethereum.getSigner() + const sidechainProvider = this.client.ethereum.getSidechainProvider() + + // parseAddress defaults to authenticated user (also if "owner" is not an address) + const ownerAddress = parseAddress(this.client, owner) + + let agentAddressList + if (Array.isArray(joinPartAgents)) { + // getAddress throws if there's an invalid address in the array + agentAddressList = joinPartAgents.map(getAddress) + } else { + // streamrNode needs to be joinPartAgent so that EE join with secret works (and join approvals from Marketplace UI) + agentAddressList = [ownerAddress] + if (this.client.options.streamrNodeAddress) { + agentAddressList.push(getAddress(this.client.options.streamrNodeAddress)) + } + } - const dataUnion = new Contract(duMainnetAddress, dataUnionMainnetABI, mainnetWallet) - // @ts-expect-error - dataUnion.deployTxReceipt = tr - // @ts-expect-error - dataUnion.sidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, sidechainProvider) - return dataUnion -} + const duMainnetAddress = await getDataUnionMainnetAddress(this.client, duName, ownerAddress, options) + const duSidechainAddress = await getDataUnionSidechainAddress(this.client, duMainnetAddress, options) -export async function getDataUnionContract(options: Todo = {}) { - const ret = getMainnetContract(this, options) - // @ts-expect-error - ret.sidechain = await getSidechainContract(this, options) - return ret -} + if (await mainnetProvider.getCode(duMainnetAddress) !== '0x') { + throw new Error(`Mainnet data union "${duName}" contract ${duMainnetAddress} already exists!`) + } -/** - * Add a new data union secret - * @param {EthereumAddress} dataUnionMainnetAddress - * @param {String} name describes the secret - * @returns {String} the server-generated secret - */ -export async function createSecret(dataUnionMainnetAddress: Todo, name: string = 'Untitled Data Union Secret') { - const duAddress = getAddress(dataUnionMainnetAddress) // throws if bad address - const url = getEndpointUrl(this.options.restUrl, 'dataunions', duAddress, 'secrets') - const res = await authFetch( - url, - this.session, - { - method: 'POST', - body: JSON.stringify({ - name - }), - headers: { - 'Content-Type': 'application/json', - }, - }, - ) - return res.secret -} + const factoryMainnetAddress = throwIfBadAddress( + options.factoryMainnetAddress || this.client.options.factoryMainnetAddress, + 'StreamrClient.options.factoryMainnetAddress' + ) + if (await mainnetProvider.getCode(factoryMainnetAddress) === '0x') { + throw new Error(`Data union factory contract not found at ${factoryMainnetAddress}, check StreamrClient.options.factoryMainnetAddress!`) + } -// ////////////////////////////////////////////////////////////////// -// admin: MANAGE DATA UNION -// ////////////////////////////////////////////////////////////////// + // function deployNewDataUnion(address owner, uint256 adminFeeFraction, address[] agents, string duName) + // @ts-expect-error + const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, mainnetWallet) + const tx = await factoryMainnet.deployNewDataUnion( + ownerAddress, + adminFeeBN, + agentAddressList, + duName, + ) + const tr = await tx.wait() + + log(`Data Union "${duName}" (mainnet: ${duMainnetAddress}, sidechain: ${duSidechainAddress}) deployed to mainnet, waiting for side-chain...`) + await until( + // @ts-expect-error + async () => await sidechainProvider.getCode(duSidechainAddress) !== '0x', + sidechainRetryTimeoutMs, + sidechainPollingIntervalMs + ) -/** - * Kick given members from data union - * @param {List} memberAddressList to kick - * @returns {Promise} partMembers sidechain transaction - */ -export async function kick(memberAddressList: Todo, options: Todo = {}) { - const members = memberAddressList.map(getAddress) // throws if there are bad addresses - const duSidechain = await getSidechainContract(this, options) - const tx = await duSidechain.partMembers(members) - // TODO: wrap promise for better error reporting in case tx fails (parse reason, throw proper error) - return tx.wait(options.confirmations || 1) -} + // @ts-expect-error + const dataUnion = new Contract(duMainnetAddress, dataUnionMainnetABI, mainnetWallet) + // @ts-expect-error + dataUnion.deployTxReceipt = tr + // @ts-expect-error + dataUnion.sidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, sidechainProvider) + return dataUnion + } -/** - * Add given Ethereum addresses as data union members - * @param {List} memberAddressList to add - * @returns {Promise} addMembers sidechain transaction - */ -export async function addMembers(memberAddressList: Todo, options: Todo = {}) { - const members = memberAddressList.map(getAddress) // throws if there are bad addresses - const duSidechain = await getSidechainContract(this, options) - const tx = await duSidechain.addMembers(members) - // TODO: wrap promise for better error reporting in case tx fails (parse reason, throw proper error) - return tx.wait(options.confirmations || 1) -} + async getDataUnionContract(options: Todo = {}) { + const ret = getMainnetContract(this.client, options) + // @ts-expect-error + ret.sidechain = await getSidechainContract(this.client, options) + return ret + } -/** - * Admin: withdraw earnings (pay gas) on behalf of a member - * TODO: add test - * @param {EthereumAddress} memberAddress the other member who gets their tokens out of the Data Union - * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) - * @returns {Promise} get receipt once withdraw transaction is confirmed - */ -export async function withdrawMember(memberAddress: Todo, options: Todo) { - const address = getAddress(memberAddress) // throws if bad address - const tr = await untilWithdrawIsComplete( - this, - this.getWithdrawMemberTx.bind(this, address), - this.getTokenBalance.bind(this, address), - { ...this.options, ...options } - ) - return tr -} + /** + * Add a new data union secret + * @param {EthereumAddress} dataUnionMainnetAddress + * @param {String} name describes the secret + * @returns {String} the server-generated secret + */ + async createSecret(dataUnionMainnetAddress: Todo, name: string = 'Untitled Data Union Secret') { + const duAddress = getAddress(dataUnionMainnetAddress) // throws if bad address + const url = getEndpointUrl(this.client.options.restUrl, 'dataunions', duAddress, 'secrets') + const res = await authFetch( + url, + this.client.session, + { + method: 'POST', + body: JSON.stringify({ + name + }), + headers: { + 'Content-Type': 'application/json', + }, + }, + ) + return res.secret + } -/** - * Admin: get the tx promise for withdrawing all earnings on behalf of a member - * @param {EthereumAddress} memberAddress the other member who gets their tokens out of the Data Union - * @param {EthereumAddress} dataUnion to withdraw my earnings from - * @param {EthereumOptions} options - * @returns {Promise} await on call .wait to actually send the tx - */ -export async function getWithdrawMemberTx(memberAddress: Todo, options: Todo) { - const a = getAddress(memberAddress) // throws if bad address - const duSidechain = await getSidechainContract(this, options) - return duSidechain.withdrawAll(a, true) // sendToMainnet=true -} + // ////////////////////////////////////////////////////////////////// + // admin: MANAGE DATA UNION + // ////////////////////////////////////////////////////////////////// + + /** + * Kick given members from data union + * @param {List} memberAddressList to kick + * @returns {Promise} partMembers sidechain transaction + */ + async kick(memberAddressList: Todo, options: Todo = {}) { + const members = memberAddressList.map(getAddress) // throws if there are bad addresses + const duSidechain = await getSidechainContract(this.client, options) + const tx = await duSidechain.partMembers(members) + // TODO: wrap promise for better error reporting in case tx fails (parse reason, throw proper error) + return tx.wait(options.confirmations || 1) + } -/** - * Admin: Withdraw a member's earnings to another address, signed by the member - * @param {EthereumAddress} dataUnion to withdraw my earnings from - * @param {EthereumAddress} memberAddress the member whose earnings are sent out - * @param {EthereumAddress} recipientAddress the address to receive the tokens in mainnet - * @param {string} signature from member, produced using signWithdrawTo - * @param {EthereumOptions} options - * @returns {Promise} get receipt once withdraw transaction is confirmed - */ -export async function withdrawToSigned(memberAddress: Todo, recipientAddress: Todo, signature: Todo, options: Todo) { - const from = getAddress(memberAddress) // throws if bad address - const to = getAddress(recipientAddress) - const tr = await untilWithdrawIsComplete( - this, - this.getWithdrawToSignedTx.bind(this, from, to, signature), - this.getTokenBalance.bind(this, to), - { ...this.options, ...options } - ) - return tr -} + /** + * Add given Ethereum addresses as data union members + * @param {List} memberAddressList to add + * @returns {Promise} addMembers sidechain transaction + */ + async addMembers(memberAddressList: Todo, options: Todo = {}) { + const members = memberAddressList.map(getAddress) // throws if there are bad addresses + const duSidechain = await getSidechainContract(this.client, options) + const tx = await duSidechain.addMembers(members) + // TODO: wrap promise for better error reporting in case tx fails (parse reason, throw proper error) + return tx.wait(options.confirmations || 1) + } -/** - * Admin: Withdraw a member's earnings to another address, signed by the member - * @param {EthereumAddress} dataUnion to withdraw my earnings from - * @param {EthereumAddress} memberAddress the member whose earnings are sent out - * @param {EthereumAddress} recipientAddress the address to receive the tokens in mainnet - * @param {string} signature from member, produced using signWithdrawTo - * @param {EthereumOptions} options - * @returns {Promise} await on call .wait to actually send the tx - */ -export async function getWithdrawToSignedTx(memberAddress: Todo, recipientAddress: Todo, signature: Todo, options: Todo) { - const duSidechain = await getSidechainContract(this, options) - return duSidechain.withdrawAllToSigned(memberAddress, recipientAddress, true, signature) // sendToMainnet=true -} + /** + * Admin: withdraw earnings (pay gas) on behalf of a member + * TODO: add test + * @param {EthereumAddress} memberAddress the other member who gets their tokens out of the Data Union + * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) + * @returns {Promise} get receipt once withdraw transaction is confirmed + */ + async withdrawMember(memberAddress: Todo, options: Todo) { + const address = getAddress(memberAddress) // throws if bad address + const tr = await untilWithdrawIsComplete( + this.client, + this.getWithdrawMemberTx.bind(this, address), + this.getTokenBalance.bind(this, address), + { ...this.client.options, ...options } + ) + return tr + } -/** - * Admin: set admin fee for the data union - * @param {number} newFeeFraction between 0.0 and 1.0 - * @param {EthereumOptions} options - */ -export async function setAdminFee(newFeeFraction: Todo, options: Todo) { - if (newFeeFraction < 0 || newFeeFraction > 1) { - throw new Error('newFeeFraction argument must be a number between 0...1, got: ' + newFeeFraction) + /** + * Admin: get the tx promise for withdrawing all earnings on behalf of a member + * @param {EthereumAddress} memberAddress the other member who gets their tokens out of the Data Union + * @param {EthereumAddress} dataUnion to withdraw my earnings from + * @param {EthereumOptions} options + * @returns {Promise} await on call .wait to actually send the tx + */ + async getWithdrawMemberTx(memberAddress: Todo, options: Todo) { + const a = getAddress(memberAddress) // throws if bad address + const duSidechain = await getSidechainContract(this.client, options) + return duSidechain.withdrawAll(a, true) // sendToMainnet=true } - const adminFeeBN = BigNumber.from((newFeeFraction * 1e18).toFixed()) // last 2...3 decimals are going to be gibberish - const duMainnet = getMainnetContract(this, options) - const tx = await duMainnet.setAdminFee(adminFeeBN) - return tx.wait() -} -/** - * Get data union admin fee fraction that admin gets from each revenue event - * @returns {number} between 0.0 and 1.0 - */ -export async function getAdminFee(options: Todo) { - const duMainnet = getMainnetContractReadOnly(this, options) - const adminFeeBN = await duMainnet.adminFeeFraction() - return +adminFeeBN.toString() / 1e18 -} + /** + * Admin: Withdraw a member's earnings to another address, signed by the member + * @param {EthereumAddress} dataUnion to withdraw my earnings from + * @param {EthereumAddress} memberAddress the member whose earnings are sent out + * @param {EthereumAddress} recipientAddress the address to receive the tokens in mainnet + * @param {string} signature from member, produced using signWithdrawTo + * @param {EthereumOptions} options + * @returns {Promise} get receipt once withdraw transaction is confirmed + */ + async withdrawToSigned(memberAddress: Todo, recipientAddress: Todo, signature: Todo, options: Todo) { + const from = getAddress(memberAddress) // throws if bad address + const to = getAddress(recipientAddress) + const tr = await untilWithdrawIsComplete( + this.client, + this.getWithdrawToSignedTx.bind(this, from, to, signature), + this.getTokenBalance.bind(this, to), + { ...this.client.options, ...options } + ) + return tr + } -export async function getAdminAddress(options: Todo) { - const duMainnet = getMainnetContractReadOnly(this, options) - return duMainnet.owner() -} + /** + * Admin: Withdraw a member's earnings to another address, signed by the member + * @param {EthereumAddress} dataUnion to withdraw my earnings from + * @param {EthereumAddress} memberAddress the member whose earnings are sent out + * @param {EthereumAddress} recipientAddress the address to receive the tokens in mainnet + * @param {string} signature from member, produced using signWithdrawTo + * @param {EthereumOptions} options + * @returns {Promise} await on call .wait to actually send the tx + */ + async getWithdrawToSignedTx(memberAddress: Todo, recipientAddress: Todo, signature: Todo, options: Todo) { + const duSidechain = await getSidechainContract(this.client, options) + return duSidechain.withdrawAllToSigned(memberAddress, recipientAddress, true, signature) // sendToMainnet=true + } -// ////////////////////////////////////////////////////////////////// -// member: JOIN & QUERY DATA UNION -// ////////////////////////////////////////////////////////////////// + /** + * Admin: set admin fee for the data union + * @param {number} newFeeFraction between 0.0 and 1.0 + * @param {EthereumOptions} options + */ + async setAdminFee(newFeeFraction: Todo, options: Todo) { + if (newFeeFraction < 0 || newFeeFraction > 1) { + throw new Error('newFeeFraction argument must be a number between 0...1, got: ' + newFeeFraction) + } + const adminFeeBN = BigNumber.from((newFeeFraction * 1e18).toFixed()) // last 2...3 decimals are going to be gibberish + const duMainnet = getMainnetContract(this.client, options) + const tx = await duMainnet.setAdminFee(adminFeeBN) + return tx.wait() + } -/** - * Send a joinRequest, or get into data union instantly with a data union secret - * @param {JoinOptions} options - * - * @typedef {object} JoinOptions - * @property {String} dataUnion Ethereum mainnet address of the data union. If not given, use one given when creating StreamrClient - * @property {String} member Ethereum mainnet address of the joining member. If not given, use StreamrClient authentication key - * @property {String} secret if given, and correct, join the data union immediately - */ -export async function joinDataUnion(options: Todo = {}) { - const { - member, - secret, - } = options - const dataUnion = getMainnetContractReadOnly(this, options) + /** + * Get data union admin fee fraction that admin gets from each revenue event + * @returns {number} between 0.0 and 1.0 + */ + async getAdminFee(options: Todo) { + const duMainnet = getMainnetContractReadOnly(this.client, options) + const adminFeeBN = await duMainnet.adminFeeFraction() + return +adminFeeBN.toString() / 1e18 + } - const body = { - memberAddress: parseAddress(this, member) + async getAdminAddress(options: Todo) { + const duMainnet = getMainnetContractReadOnly(this.client, options) + return duMainnet.owner() } - // @ts-expect-error - if (secret) { body.secret = secret } - - const url = getEndpointUrl(this.options.restUrl, 'dataunions', dataUnion.address, 'joinRequests') - return authFetch( - url, - this.session, - { - method: 'POST', - body: JSON.stringify(body), - headers: { - 'Content-Type': 'application/json', + + // ////////////////////////////////////////////////////////////////// + // member: JOIN & QUERY DATA UNION + // ////////////////////////////////////////////////////////////////// + + /** + * Send a joinRequest, or get into data union instantly with a data union secret + * @param {JoinOptions} options + * + * @typedef {object} JoinOptions + * @property {String} dataUnion Ethereum mainnet address of the data union. If not given, use one given when creating StreamrClient + * @property {String} member Ethereum mainnet address of the joining member. If not given, use StreamrClient authentication key + * @property {String} secret if given, and correct, join the data union immediately + */ + async joinDataUnion(options: Todo = {}) { + const { + member, + secret, + } = options + const dataUnion = getMainnetContractReadOnly(this.client, options) + + const body = { + memberAddress: parseAddress(this.client, member) + } + // @ts-expect-error + if (secret) { body.secret = secret } + + const url = getEndpointUrl(this.client.options.restUrl, 'dataunions', dataUnion.address, 'joinRequests') + return authFetch( + url, + this.client.session, + { + method: 'POST', + body: JSON.stringify(body), + headers: { + 'Content-Type': 'application/json', + }, }, - }, - ) -} + ) + } -/** - * Await this function when you want to make sure a member is accepted in the data union - * @param {EthereumAddress} memberAddress (optional, default is StreamrClient's auth: privateKey) - * @param {Number} pollingIntervalMs (optional, default: 1000) ask server if member is in - * @param {Number} retryTimeoutMs (optional, default: 60000) give up - * @return {Promise} resolves when member is in the data union (or fails with HTTP error) - */ -export async function hasJoined(memberAddress: Todo, options: Todo = {}) { - const { - pollingIntervalMs = 1000, - retryTimeoutMs = 60000, - } = options - const address = parseAddress(this, memberAddress) - const duSidechain = await getSidechainContractReadOnly(this, options) + /** + * Await this function when you want to make sure a member is accepted in the data union + * @param {EthereumAddress} memberAddress (optional, default is StreamrClient's auth: privateKey) + * @param {Number} pollingIntervalMs (optional, default: 1000) ask server if member is in + * @param {Number} retryTimeoutMs (optional, default: 60000) give up + * @return {Promise} resolves when member is in the data union (or fails with HTTP error) + */ + async hasJoined(memberAddress: Todo, options: Todo = {}) { + const { + pollingIntervalMs = 1000, + retryTimeoutMs = 60000, + } = options + const address = parseAddress(this.client, memberAddress) + const duSidechain = await getSidechainContractReadOnly(this.client, options) + + // memberData[0] is enum ActiveStatus {None, Active, Inactive}, and zero means member has never joined + await until(async () => (await duSidechain.memberData(address))[0] !== 0, retryTimeoutMs, pollingIntervalMs) + } - // memberData[0] is enum ActiveStatus {None, Active, Inactive}, and zero means member has never joined - await until(async () => (await duSidechain.memberData(address))[0] !== 0, retryTimeoutMs, pollingIntervalMs) -} + // TODO: this needs more thought: probably something like getEvents from sidechain? Heavy on RPC? + async getMembers(options: Todo) { + const duSidechain = await getSidechainContractReadOnly(this.client, options) + throw new Error(`Not implemented for side-chain data union (at ${duSidechain.address})`) + // event MemberJoined(address indexed); + // event MemberParted(address indexed); + } -// TODO: this needs more thought: probably something like getEvents from sidechain? Heavy on RPC? -export async function getMembers(options: Todo) { - const duSidechain = await getSidechainContractReadOnly(this, options) - throw new Error(`Not implemented for side-chain data union (at ${duSidechain.address})`) - // event MemberJoined(address indexed); - // event MemberParted(address indexed); -} + async getDataUnionStats(options: Todo) { + const duSidechain = await getSidechainContractReadOnly(this.client, options) + const [ + totalEarnings, + totalEarningsWithdrawn, + activeMemberCount, + inactiveMemberCount, + lifetimeMemberEarnings, + joinPartAgentCount, + ] = await duSidechain.getStats() + const totalWithdrawable = totalEarnings.sub(totalEarningsWithdrawn) + return { + activeMemberCount, + inactiveMemberCount, + joinPartAgentCount, + totalEarnings, + totalWithdrawable, + lifetimeMemberEarnings, + } + } -export async function getDataUnionStats(options: Todo) { - const duSidechain = await getSidechainContractReadOnly(this, options) - const [ - totalEarnings, - totalEarningsWithdrawn, - activeMemberCount, - inactiveMemberCount, - lifetimeMemberEarnings, - joinPartAgentCount, - ] = await duSidechain.getStats() - const totalWithdrawable = totalEarnings.sub(totalEarningsWithdrawn) - return { - activeMemberCount, - inactiveMemberCount, - joinPartAgentCount, - totalEarnings, - totalWithdrawable, - lifetimeMemberEarnings, + /** + * Get stats of a single data union member + * @param {EthereumAddress} dataUnion to query + * @param {EthereumAddress} memberAddress (optional) if not supplied, get the stats of currently logged in StreamrClient (if auth: privateKey) + */ + async getMemberStats(memberAddress: Todo, options: Todo) { + const address = parseAddress(this.client, memberAddress) + // TODO: use duSidechain.getMemberStats(address) once it's implemented, to ensure atomic read + // (so that memberData is from same block as getEarnings, otherwise withdrawable will be foobar) + const duSidechain = await getSidechainContractReadOnly(this.client, options) + const mdata = await duSidechain.memberData(address) + const total = await duSidechain.getEarnings(address).catch(() => 0) + const withdrawnEarnings = mdata[3].toString() + const withdrawable = total ? total.sub(withdrawnEarnings) : 0 + return { + status: ['unknown', 'active', 'inactive', 'blocked'][mdata[0]], + earningsBeforeLastJoin: mdata[1].toString(), + lmeAtJoin: mdata[2].toString(), + totalEarnings: total.toString(), + withdrawableEarnings: withdrawable.toString(), + } } -} -/** - * Get stats of a single data union member - * @param {EthereumAddress} dataUnion to query - * @param {EthereumAddress} memberAddress (optional) if not supplied, get the stats of currently logged in StreamrClient (if auth: privateKey) - */ -export async function getMemberStats(memberAddress: Todo, options: Todo) { - const address = parseAddress(this, memberAddress) - // TODO: use duSidechain.getMemberStats(address) once it's implemented, to ensure atomic read - // (so that memberData is from same block as getEarnings, otherwise withdrawable will be foobar) - const duSidechain = await getSidechainContractReadOnly(this, options) - const mdata = await duSidechain.memberData(address) - const total = await duSidechain.getEarnings(address).catch(() => 0) - const withdrawnEarnings = mdata[3].toString() - const withdrawable = total ? total.sub(withdrawnEarnings) : 0 - return { - status: ['unknown', 'active', 'inactive', 'blocked'][mdata[0]], - earningsBeforeLastJoin: mdata[1].toString(), - lmeAtJoin: mdata[2].toString(), - totalEarnings: total.toString(), - withdrawableEarnings: withdrawable.toString(), + /** + * Get the amount of tokens the member would get from a successful withdraw + * @param dataUnion to query + * @param memberAddress whose balance is returned + * @return {Promise} + */ + async getMemberBalance(memberAddress: Todo, options: Todo) { + const address = parseAddress(this.client, memberAddress) + const duSidechain = await getSidechainContractReadOnly(this.client, options) + return duSidechain.getWithdrawableEarnings(address) } -} -/** - * Get the amount of tokens the member would get from a successful withdraw - * @param dataUnion to query - * @param memberAddress whose balance is returned - * @return {Promise} - */ -export async function getMemberBalance(memberAddress: Todo, options: Todo) { - const address = parseAddress(this, memberAddress) - const duSidechain = await getSidechainContractReadOnly(this, options) - return duSidechain.getWithdrawableEarnings(address) -} + /** + * Get token balance for given address + * @param {EthereumAddress} address + * @param options such as tokenAddress. If not given, then first check if + * dataUnion was given in StreamrClient constructor, then check if tokenAddress + * was given in StreamrClient constructor. + * @returns {Promise} token balance in "wei" (10^-18 parts) + */ + async getTokenBalance(address: Todo, options: Todo) { + const a = parseAddress(this.client, address) + const tokenAddressMainnet = options.tokenAddress || ( + await getMainnetContractReadOnly(this.client, options).then((c: Todo) => c.token()).catch(() => null) || this.client.options.tokenAddress + ) + if (!tokenAddressMainnet) { throw new Error('tokenAddress option not found') } + const provider = this.client.ethereum.getMainnetProvider() + const token = new Contract(tokenAddressMainnet, [{ + name: 'balanceOf', + inputs: [{ type: 'address' }], + outputs: [{ type: 'uint256' }], + constant: true, + payable: false, + stateMutability: 'view', + type: 'function' + }], provider) + return token.balanceOf(a) + } -/** - * Get token balance for given address - * @param {EthereumAddress} address - * @param options such as tokenAddress. If not given, then first check if - * dataUnion was given in StreamrClient constructor, then check if tokenAddress - * was given in StreamrClient constructor. - * @returns {Promise} token balance in "wei" (10^-18 parts) - */ -export async function getTokenBalance(address: Todo, options: Todo) { - const a = parseAddress(this, address) - const tokenAddressMainnet = options.tokenAddress || ( - await getMainnetContractReadOnly(this, options).then((c: Todo) => c.token()).catch(() => null) || this.options.tokenAddress - ) - if (!tokenAddressMainnet) { throw new Error('tokenAddress option not found') } - const provider = this.ethereum.getMainnetProvider() - const token = new Contract(tokenAddressMainnet, [{ - name: 'balanceOf', - inputs: [{ type: 'address' }], - outputs: [{ type: 'uint256' }], - constant: true, - payable: false, - stateMutability: 'view', - type: 'function' - }], provider) - return token.balanceOf(a) -} + /** + * Figure out if given mainnet address is old DataUnion (v 1.0) or current 2.0 + * NOTE: Current version of streamr-client-javascript can only handle current version! + * @param {EthereumAddress} contractAddress + * @returns {number} 1 for old, 2 for current, zero for "not a data union" + */ + async getDataUnionVersion(contractAddress: Todo) { + const a = getAddress(contractAddress) // throws if bad address + const provider = this.client.ethereum.getMainnetProvider() + const du = new Contract(a, [{ + name: 'version', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }], provider) + try { + const version = await du.version() + return +version + } catch (e) { + return 0 + } + } -/** - * Figure out if given mainnet address is old DataUnion (v 1.0) or current 2.0 - * NOTE: Current version of streamr-client-javascript can only handle current version! - * @param {EthereumAddress} contractAddress - * @returns {number} 1 for old, 2 for current, zero for "not a data union" - */ -export async function getDataUnionVersion(contractAddress: Todo) { - const a = getAddress(contractAddress) // throws if bad address - const provider = this.ethereum.getMainnetProvider() - const du = new Contract(a, [{ - name: 'version', - inputs: [], - outputs: [{ type: 'uint256' }], - stateMutability: 'view', - type: 'function' - }], provider) - try { - const version = await du.version() - return +version - } catch (e) { - return 0 + // ////////////////////////////////////////////////////////////////// + // member: WITHDRAW EARNINGS + // ////////////////////////////////////////////////////////////////// + + /** + * Withdraw all your earnings + * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) + * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) + */ + async withdraw(options: Todo = {}) { + const tr = await untilWithdrawIsComplete( + this.client, + this.getWithdrawTx.bind(this), + this.getTokenBalance.bind(this, null), // null means this StreamrClient's auth credentials + { ...this.client.options, ...options } + ) + return tr } -} -// ////////////////////////////////////////////////////////////////// -// member: WITHDRAW EARNINGS -// ////////////////////////////////////////////////////////////////// + /** + * Get the tx promise for withdrawing all your earnings + * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) + * @returns {Promise} await on call .wait to actually send the tx + */ + async getWithdrawTx(options: Todo) { + const signer = await this.client.ethereum.getSidechainSigner() + // @ts-expect-error + const address = await signer.getAddress() + const duSidechain = await getSidechainContract(this.client, options) -/** - * Withdraw all your earnings - * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) - * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) - */ -export async function withdraw(options: Todo = {}) { - const tr = await untilWithdrawIsComplete( - this, - this.getWithdrawTx.bind(this), - this.getTokenBalance.bind(this, null), // null means this StreamrClient's auth credentials - { ...this.options, ...options } - ) - return tr -} + const withdrawable = await duSidechain.getWithdrawableEarnings(address) + if (withdrawable.eq(0)) { + throw new Error(`${address} has nothing to withdraw in (sidechain) data union ${duSidechain.address}`) + } -/** - * Get the tx promise for withdrawing all your earnings - * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) - * @returns {Promise} await on call .wait to actually send the tx - */ -export async function getWithdrawTx(options: Todo) { - const signer = await this.ethereum.getSidechainSigner() - const address = await signer.getAddress() - const duSidechain = await getSidechainContract(this, options) - - const withdrawable = await duSidechain.getWithdrawableEarnings(address) - if (withdrawable.eq(0)) { - throw new Error(`${address} has nothing to withdraw in (sidechain) data union ${duSidechain.address}`) + if (this.client.options.minimumWithdrawTokenWei && withdrawable.lt(this.client.options.minimumWithdrawTokenWei)) { + throw new Error(`${address} has only ${withdrawable} to withdraw in ` + + `(sidechain) data union ${duSidechain.address} (min: ${this.client.options.minimumWithdrawTokenWei})`) + } + return duSidechain.withdrawAll(address, true) // sendToMainnet=true } - if (this.options.minimumWithdrawTokenWei && withdrawable.lt(this.options.minimumWithdrawTokenWei)) { - throw new Error(`${address} has only ${withdrawable} to withdraw in ` - + `(sidechain) data union ${duSidechain.address} (min: ${this.options.minimumWithdrawTokenWei})`) + /** + * Withdraw earnings and "donate" them to the given address + * @param {EthereumAddress} recipientAddress the address to receive the tokens + * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) + * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) + */ + async withdrawTo(recipientAddress: Todo, options = {}) { + const to = getAddress(recipientAddress) // throws if bad address + const tr = await untilWithdrawIsComplete( + this.client, + this.getWithdrawTxTo.bind(this, to), + this.getTokenBalance.bind(this, to), + { ...this.client.options, ...options } + ) + return tr } - return duSidechain.withdrawAll(address, true) // sendToMainnet=true -} -/** - * Withdraw earnings and "donate" them to the given address - * @param {EthereumAddress} recipientAddress the address to receive the tokens - * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) - * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) - */ -export async function withdrawTo(recipientAddress: Todo, options = {}) { - const to = getAddress(recipientAddress) // throws if bad address - const tr = await untilWithdrawIsComplete( - this, - this.getWithdrawTxTo.bind(this, to), - this.getTokenBalance.bind(this, to), - { ...this.options, ...options } - ) - return tr -} + /** + * Withdraw earnings and "donate" them to the given address + * @param {EthereumAddress} recipientAddress the address to receive the tokens + * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) + * @returns {Promise} await on call .wait to actually send the tx + */ + async getWithdrawTxTo(recipientAddress: Todo, options: Todo) { + const signer = await this.client.ethereum.getSidechainSigner() + // @ts-expect-error + const address = await signer.getAddress() + const duSidechain = await getSidechainContract(this.client, options) + const withdrawable = await duSidechain.getWithdrawableEarnings(address) + if (withdrawable.eq(0)) { + throw new Error(`${address} has nothing to withdraw in (sidechain) data union ${duSidechain.address}`) + } + return duSidechain.withdrawAllTo(recipientAddress, true) // sendToMainnet=true + } -/** - * Withdraw earnings and "donate" them to the given address - * @param {EthereumAddress} recipientAddress the address to receive the tokens - * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) - * @returns {Promise} await on call .wait to actually send the tx - */ -export async function getWithdrawTxTo(recipientAddress: Todo, options: Todo) { - const signer = await this.ethereum.getSidechainSigner() - const address = await signer.getAddress() - const duSidechain = await getSidechainContract(this, options) - const withdrawable = await duSidechain.getWithdrawableEarnings(address) - if (withdrawable.eq(0)) { - throw new Error(`${address} has nothing to withdraw in (sidechain) data union ${duSidechain.address}`) + /** + * Member can sign off to "donate" all earnings to another address such that someone else + * can submit the transaction (and pay for the gas) + * This signature is only valid until next withdrawal takes place (using this signature or otherwise). + * Note that while it's a "blank cheque" for withdrawing all earnings at the moment it's used, it's + * invalidated by the first withdraw after signing it. In other words, any signature can be invalidated + * by making a "normal" withdraw e.g. `await streamrClient.withdraw()` + * Admin can execute the withdraw using this signature: ``` + * await adminStreamrClient.withdrawToSigned(memberAddress, recipientAddress, signature) + * ``` + * @param {EthereumAddress} recipientAddress the address authorized to receive the tokens + * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) + * @returns {string} signature authorizing withdrawing all earnings to given recipientAddress + */ + async signWithdrawTo(recipientAddress: Todo, options: Todo) { + return this.signWithdrawAmountTo(recipientAddress, BigNumber.from(0), options) } - return duSidechain.withdrawAllTo(recipientAddress, true) // sendToMainnet=true -} -/** - * Member can sign off to "donate" all earnings to another address such that someone else - * can submit the transaction (and pay for the gas) - * This signature is only valid until next withdrawal takes place (using this signature or otherwise). - * Note that while it's a "blank cheque" for withdrawing all earnings at the moment it's used, it's - * invalidated by the first withdraw after signing it. In other words, any signature can be invalidated - * by making a "normal" withdraw e.g. `await streamrClient.withdraw()` - * Admin can execute the withdraw using this signature: ``` - * await adminStreamrClient.withdrawToSigned(memberAddress, recipientAddress, signature) - * ``` - * @param {EthereumAddress} recipientAddress the address authorized to receive the tokens - * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) - * @returns {string} signature authorizing withdrawing all earnings to given recipientAddress - */ -export async function signWithdrawTo(recipientAddress: Todo, options: Todo) { - return this.signWithdrawAmountTo(recipientAddress, BigNumber.from(0), options) + /** + * Member can sign off to "donate" specific amount of earnings to another address such that someone else + * can submit the transaction (and pay for the gas) + * This signature is only valid until next withdrawal takes place (using this signature or otherwise). + * @param {EthereumAddress} recipientAddress the address authorized to receive the tokens + * @param {BigNumber|number|string} amountTokenWei that the signature is for (can't be used for less or for more) + * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) + * @returns {string} signature authorizing withdrawing all earnings to given recipientAddress + */ + async signWithdrawAmountTo(recipientAddress: Todo, amountTokenWei: Todo, options: Todo) { + const to = getAddress(recipientAddress) // throws if bad address + const signer = this.client.ethereum.getSigner() // it shouldn't matter if it's mainnet or sidechain signer since key should be the same + // @ts-expect-error + const address = await signer.getAddress() + const duSidechain = await getSidechainContractReadOnly(this.client, options) + const memberData = await duSidechain.memberData(address) + if (memberData[0] === '0') { throw new Error(`${address} is not a member in Data Union (sidechain address ${duSidechain.address})`) } + const withdrawn = memberData[3] + const message = to + hexZeroPad(amountTokenWei, 32).slice(2) + duSidechain.address.slice(2) + hexZeroPad(withdrawn, 32).slice(2) + // @ts-expect-error + const signature = await signer.signMessage(arrayify(message)) + return signature + } } -/** - * Member can sign off to "donate" specific amount of earnings to another address such that someone else - * can submit the transaction (and pay for the gas) - * This signature is only valid until next withdrawal takes place (using this signature or otherwise). - * @param {EthereumAddress} recipientAddress the address authorized to receive the tokens - * @param {BigNumber|number|string} amountTokenWei that the signature is for (can't be used for less or for more) - * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) - * @returns {string} signature authorizing withdrawing all earnings to given recipientAddress - */ -export async function signWithdrawAmountTo(recipientAddress: Todo, amountTokenWei: Todo, options: Todo) { - const to = getAddress(recipientAddress) // throws if bad address - const signer = this.ethereum.getSigner() // it shouldn't matter if it's mainnet or sidechain signer since key should be the same - const address = await signer.getAddress() - const duSidechain = await getSidechainContractReadOnly(this, options) - const memberData = await duSidechain.memberData(address) - if (memberData[0] === '0') { throw new Error(`${address} is not a member in Data Union (sidechain address ${duSidechain.address})`) } - const withdrawn = memberData[3] - const message = to + hexZeroPad(amountTokenWei, 32).slice(2) + duSidechain.address.slice(2) + hexZeroPad(withdrawn, 32).slice(2) - const signature = await signer.signMessage(arrayify(message)) - return signature -} diff --git a/src/rest/LoginEndpoints.ts b/src/rest/LoginEndpoints.ts index 24f66ecc6..910c3b3d2 100644 --- a/src/rest/LoginEndpoints.ts +++ b/src/rest/LoginEndpoints.ts @@ -1,3 +1,4 @@ +import StreamrClient from '../StreamrClient' import { Todo } from '../types' import { getEndpointUrl } from '../utils' @@ -17,85 +18,94 @@ async function getSessionToken(url: Todo, props: Todo) { ) } -export async function getChallenge(address: Todo) { - this.debug('getChallenge %o', { - address, - }) - const url = getEndpointUrl(this.options.restUrl, 'login', 'challenge', address) - return authFetch( - url, - undefined, - { - method: 'POST', - }, - ) -} +export class LoginEndpoints { + + client: StreamrClient -export async function sendChallengeResponse(challenge: Todo, signature: Todo, address: Todo) { - this.debug('sendChallengeResponse %o', { - challenge, - signature, - address, - }) - const url = getEndpointUrl(this.options.restUrl, 'login', 'response') - const props = { - challenge, - signature, - address, + constructor(client: StreamrClient) { + this.client = client } - return getSessionToken(url, props) -} -export async function loginWithChallengeResponse(signingFunction: Todo, address: Todo) { - this.debug('loginWithChallengeResponse %o', { - address, - }) - const challenge = await this.getChallenge(address) - const signature = await signingFunction(challenge.challenge) - return this.sendChallengeResponse(challenge, signature, address) -} + async getChallenge(address: Todo) { + this.client.debug('getChallenge %o', { + address, + }) + const url = getEndpointUrl(this.client.options.restUrl, 'login', 'challenge', address) + return authFetch( + url, + undefined, + { + method: 'POST', + }, + ) + } -export async function loginWithApiKey(apiKey: Todo) { - this.debug('loginWithApiKey %o', { - apiKey, - }) - const url = getEndpointUrl(this.options.restUrl, 'login', 'apikey') - const props = { - apiKey, + async sendChallengeResponse(challenge: Todo, signature: Todo, address: Todo) { + this.client.debug('sendChallengeResponse %o', { + challenge, + signature, + address, + }) + const url = getEndpointUrl(this.client.options.restUrl, 'login', 'response') + const props = { + challenge, + signature, + address, + } + return getSessionToken(url, props) } - return getSessionToken(url, props) -} -export async function loginWithUsernamePassword(username: Todo, password: Todo) { - this.debug('loginWithUsernamePassword %o', { - username, - }) - const url = getEndpointUrl(this.options.restUrl, 'login', 'password') - const props = { - username, - password, + async loginWithChallengeResponse(signingFunction: Todo, address: Todo) { + this.client.debug('loginWithChallengeResponse %o', { + address, + }) + const challenge = await this.getChallenge(address) + const signature = await signingFunction(challenge.challenge) + return this.sendChallengeResponse(challenge, signature, address) } - try { - return await getSessionToken(url, props) - } catch (err) { - if (err && err.response && err.response.status === 404) { - // this 404s if running against new backend with username/password support removed - // wrap with appropriate error message - const message = 'username/password auth is no longer supported. Please create an ethereum identity.' - throw new AuthFetchError(message, err.response, err.body) + + async loginWithApiKey(apiKey: Todo) { + this.client.debug('loginWithApiKey %o', { + apiKey, + }) + const url = getEndpointUrl(this.client.options.restUrl, 'login', 'apikey') + const props = { + apiKey, } - throw err + return getSessionToken(url, props) } -} -export async function getUserInfo() { - this.debug('getUserInfo') - return authFetch(`${this.options.restUrl}/users/me`, this.session) -} + async loginWithUsernamePassword(username: Todo, password: Todo) { + this.client.debug('loginWithUsernamePassword %o', { + username, + }) + const url = getEndpointUrl(this.client.options.restUrl, 'login', 'password') + const props = { + username, + password, + } + try { + return await getSessionToken(url, props) + } catch (err) { + if (err && err.response && err.response.status === 404) { + // this 404s if running against new backend with username/password support removed + // wrap with appropriate error message + const message = 'username/password auth is no longer supported. Please create an ethereum identity.' + throw new AuthFetchError(message, err.response, err.body) + } + throw err + } + } + + async getUserInfo() { + this.client.debug('getUserInfo') + return authFetch(`${this.client.options.restUrl}/users/me`, this.client.session) + } -export async function logoutEndpoint() { - this.debug('logoutEndpoint') - return authFetch(`${this.options.restUrl}/logout`, this.session, { - method: 'POST', - }) + async logoutEndpoint() { + this.client.debug('logoutEndpoint') + return authFetch(`${this.client.options.restUrl}/logout`, this.client.session, { + method: 'POST', + }) + } } diff --git a/src/rest/StreamEndpoints.ts b/src/rest/StreamEndpoints.ts index c71320d8d..3b58d1f5f 100644 --- a/src/rest/StreamEndpoints.ts +++ b/src/rest/StreamEndpoints.ts @@ -12,6 +12,7 @@ import { isKeyExchangeStream } from '../stream/KeyExchange' import authFetch from './authFetch' import { Todo } from '../types' +import StreamrClient from '../StreamrClient' const debug = debugFactory('StreamrClient') @@ -37,203 +38,209 @@ function getKeepAliveAgentForUrl(url: string) { throw new Error(`Unknown protocol in URL: ${url}`) } -// These function are mixed in to StreamrClient.prototype. -// In the below functions, 'this' is intended to be the StreamrClient -export async function getStream(streamId: Todo) { - this.debug('getStream %o', { - streamId, - }) - - if (isKeyExchangeStream(streamId)) { - return new Stream(this, { - id: streamId, - partitions: 1, - }) - } +export class StreamEndpoints { - const url = getEndpointUrl(this.options.restUrl, 'streams', streamId) - try { - const json = await authFetch(url, this.session) - return new Stream(this, json) - } catch (e) { - if (e.response && e.response.status === 404) { - return undefined - } - throw e + client: StreamrClient + + constructor(client: StreamrClient) { + this.client = client } -} -export async function listStreams(query: Todo = {}) { - this.debug('listStreams %o', { - query, - }) - const url = getEndpointUrl(this.options.restUrl, 'streams') + '?' + qs.stringify(query) - const json = await authFetch(url, this.session) - return json ? json.map((stream) => new Stream(this, stream)) : [] -} + async getStream(streamId: Todo) { + this.client.debug('getStream %o', { + streamId, + }) -export async function getStreamByName(name: string) { - this.debug('getStreamByName %o', { - name, - }) - const json = await this.listStreams({ - name, - public: false, - }) - return json[0] ? new Stream(this, json[0]) : undefined -} + if (isKeyExchangeStream(streamId)) { + return new Stream(this.client, { + id: streamId, + partitions: 1, + }) + } -export async function createStream(props: Todo) { - this.debug('createStream %o', { - props, - }) - - const json = await authFetch( - getEndpointUrl(this.options.restUrl, 'streams'), - this.session, - { - method: 'POST', - body: JSON.stringify(props), - }, - ) - return json ? new Stream(this, json) : undefined -} + const url = getEndpointUrl(this.client.options.restUrl, 'streams', streamId) + try { + const json = await authFetch(url, this.client.session) + return new Stream(this.client, json) + } catch (e) { + if (e.response && e.response.status === 404) { + return undefined + } + throw e + } + } -export async function getOrCreateStream(props: Todo) { - this.debug('getOrCreateStream %o', { - props, - }) - let json + async listStreams(query: Todo = {}) { + this.client.debug('listStreams %o', { + query, + }) + const url = getEndpointUrl(this.client.options.restUrl, 'streams') + '?' + qs.stringify(query) + const json = await authFetch(url, this.client.session) + return json ? json.map((stream: any) => new Stream(this.client, stream)) : [] + } - // Try looking up the stream by id or name, whichever is defined - if (props.id) { - json = await this.getStream(props.id) - } else if (props.name) { - json = await this.getStreamByName(props.name) + async getStreamByName(name: string) { + this.client.debug('getStreamByName %o', { + name, + }) + const json = await this.listStreams({ + name, + public: false, + }) + return json[0] ? new Stream(this.client, json[0]) : undefined } - // If not found, try creating the stream - if (!json) { - json = await this.createStream(props) - debug('Created stream: %s (%s)', props.name, json.id) + async createStream(props: Todo) { + this.client.debug('createStream %o', { + props, + }) + + const json = await authFetch( + getEndpointUrl(this.client.options.restUrl, 'streams'), + this.client.session, + { + method: 'POST', + body: JSON.stringify(props), + }, + ) + return json ? new Stream(this.client, json) : undefined } - // If still nothing, throw - if (!json) { - throw new Error(`Unable to find or create stream: ${props.name || props.id}`) - } else { - return new Stream(this, json) + async getOrCreateStream(props: Todo) { + this.client.debug('getOrCreateStream %o', { + props, + }) + let json: any + + // Try looking up the stream by id or name, whichever is defined + if (props.id) { + json = await this.getStream(props.id) + } else if (props.name) { + json = await this.getStreamByName(props.name) + } + + // If not found, try creating the stream + if (!json) { + json = await this.createStream(props) + debug('Created stream: %s (%s)', props.name, json.id) + } + + // If still nothing, throw + if (!json) { + throw new Error(`Unable to find or create stream: ${props.name || props.id}`) + } else { + return new Stream(this.client, json) + } } -} -export async function getStreamPublishers(streamId: Todo) { - this.debug('getStreamPublishers %o', { - streamId, - }) - const url = getEndpointUrl(this.options.restUrl, 'streams', streamId, 'publishers') - const json = await authFetch(url, this.session) - return json.addresses.map((a: string) => a.toLowerCase()) -} + async getStreamPublishers(streamId: Todo) { + this.client.debug('getStreamPublishers %o', { + streamId, + }) + const url = getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'publishers') + const json = await authFetch(url, this.client.session) + return json.addresses.map((a: string) => a.toLowerCase()) + } -export async function isStreamPublisher(streamId: Todo, ethAddress: Todo) { - this.debug('isStreamPublisher %o', { - streamId, - ethAddress, - }) - const url = getEndpointUrl(this.options.restUrl, 'streams', streamId, 'publisher', ethAddress) - try { - await authFetch(url, this.session) - return true - } catch (e) { - this.debug(e) - if (e.response && e.response.status === 404) { - return false + async isStreamPublisher(streamId: Todo, ethAddress: Todo) { + this.client.debug('isStreamPublisher %o', { + streamId, + ethAddress, + }) + const url = getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'publisher', ethAddress) + try { + await authFetch(url, this.client.session) + return true + } catch (e) { + this.client.debug(e) + if (e.response && e.response.status === 404) { + return false + } + throw e } - throw e } -} -export async function getStreamSubscribers(streamId: Todo) { - this.debug('getStreamSubscribers %o', { - streamId, - }) - const url = getEndpointUrl(this.options.restUrl, 'streams', streamId, 'subscribers') - const json = await authFetch(url, this.session) - return json.addresses.map((a: Todo) => a.toLowerCase()) -} + async getStreamSubscribers(streamId: Todo) { + this.client.debug('getStreamSubscribers %o', { + streamId, + }) + const url = getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'subscribers') + const json = await authFetch(url, this.client.session) + return json.addresses.map((a: Todo) => a.toLowerCase()) + } -export async function isStreamSubscriber(streamId: Todo, ethAddress: Todo) { - this.debug('isStreamSubscriber %o', { - streamId, - ethAddress, - }) - const url = getEndpointUrl(this.options.restUrl, 'streams', streamId, 'subscriber', ethAddress) - try { - await authFetch(url, this.session) - return true - } catch (e) { - if (e.response && e.response.status === 404) { - return false + async isStreamSubscriber(streamId: Todo, ethAddress: Todo) { + this.client.debug('isStreamSubscriber %o', { + streamId, + ethAddress, + }) + const url = getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'subscriber', ethAddress) + try { + await authFetch(url, this.client.session) + return true + } catch (e) { + if (e.response && e.response.status === 404) { + return false + } + throw e } - throw e } -} -export async function getStreamValidationInfo(streamId: Todo) { - this.debug('getStreamValidationInfo %o', { - streamId, - }) - const url = getEndpointUrl(this.options.restUrl, 'streams', streamId, 'validation') - const json = await authFetch(url, this.session) - return json -} + async getStreamValidationInfo(streamId: Todo) { + this.client.debug('getStreamValidationInfo %o', { + streamId, + }) + const url = getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'validation') + const json = await authFetch(url, this.client.session) + return json + } + + async getStreamLast(streamObjectOrId: Todo) { + const { streamId, streamPartition = 0, count = 1 } = validateOptions(streamObjectOrId) + this.client.debug('getStreamLast %o', { + streamId, + streamPartition, + count, + }) + const query = { + count, + } -export async function getStreamLast(streamObjectOrId: Todo) { - const { streamId, streamPartition = 0, count = 1 } = validateOptions(streamObjectOrId) - this.debug('getStreamLast %o', { - streamId, - streamPartition, - count, - }) - const query = { - count, + const url = getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'data', 'partitions', streamPartition, 'last') + `?${qs.stringify(query)}` + const json = await authFetch(url, this.client.session) + return json } - const url = getEndpointUrl(this.options.restUrl, 'streams', streamId, 'data', 'partitions', streamPartition, 'last') + `?${qs.stringify(query)}` - const json = await authFetch(url, this.session) - return json -} + async getStreamPartsByStorageNode(address: Todo) { + const json = await authFetch(getEndpointUrl(this.client.options.restUrl, 'storageNodes', address, 'streams'), this.client.session) + let result: Todo = [] + json.forEach((stream: Todo) => { + result = result.concat(StreamPart.fromStream(stream)) + }) + return result + } -export async function getStreamPartsByStorageNode(address: Todo) { - const json = await authFetch(getEndpointUrl(this.options.restUrl, 'storageNodes', address, 'streams'), this.session) - let result: Todo = [] - json.forEach((stream: Todo) => { - result = result.concat(StreamPart.fromStream(stream)) - }) - return result -} + async publishHttp(streamObjectOrId: Todo, data: Todo, requestOptions: Todo = {}, keepAlive: Todo = true) { + let streamId + if (streamObjectOrId instanceof Stream) { + streamId = streamObjectOrId.id + } else { + streamId = streamObjectOrId + } + this.client.debug('publishHttp %o', { + streamId, data, + }) -export async function publishHttp(streamObjectOrId: Todo, data: Todo, requestOptions: Todo = {}, keepAlive: Todo = true) { - let streamId - if (streamObjectOrId instanceof Stream) { - // @ts-expect-error - streamId = streamObjectOrId.id - } else { - streamId = streamObjectOrId - } - this.debug('publishHttp %o', { - streamId, data, - }) - - // Send data to the stream - return authFetch( - getEndpointUrl(this.options.restUrl, 'streams', streamId, 'data'), - this.session, - { - ...requestOptions, - method: 'POST', - body: JSON.stringify(data), - agent: keepAlive ? getKeepAliveAgentForUrl(this.options.restUrl) : undefined, - }, - ) + // Send data to the stream + return authFetch( + getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'data'), + this.client.session, + { + ...requestOptions, + method: 'POST', + body: JSON.stringify(data), + agent: keepAlive ? getKeepAliveAgentForUrl(this.client.options.restUrl) : undefined, + }, + ) + } } diff --git a/src/stream/index.js b/src/stream/index.ts similarity index 84% rename from src/stream/index.js rename to src/stream/index.ts index cd1f1d658..37117a808 100644 --- a/src/stream/index.js +++ b/src/stream/index.ts @@ -2,9 +2,17 @@ import { getEndpointUrl } from '../utils' import authFetch from '../rest/authFetch' import StorageNode from './StorageNode' +import StreamrClient from '../StreamrClient' +import { Todo } from '../types' export default class Stream { - constructor(client, props) { + + // TODO add field definitions for all fields + // @ts-expect-error + id: string + _client: StreamrClient + + constructor(client: StreamrClient, props: Todo) { this._client = client Object.assign(this, props) } @@ -25,6 +33,7 @@ export default class Stream { const result = {} Object.keys(this).forEach((key) => { if (!key.startsWith('_')) { + // @ts-expect-error result[key] = this[key] } }) @@ -55,13 +64,13 @@ export default class Stream { ) } - async hasPermission(operation, userId) { + async hasPermission(operation: Todo, userId: Todo) { // eth addresses may be in checksumcase, but userId from server has no case const userIdCaseInsensitive = typeof userId === 'string' ? userId.toLowerCase() : undefined // if not string then undefined const permissions = await this.getPermissions() - return permissions.find((p) => { + return permissions.find((p: Todo) => { if (p.operation !== operation) { return false } if (userIdCaseInsensitive === undefined) { @@ -71,8 +80,8 @@ export default class Stream { }) } - async grantPermission(operation, userId) { - const permissionObject = { + async grantPermission(operation: Todo, userId: Todo) { + const permissionObject: Todo = { operation, } @@ -94,7 +103,7 @@ export default class Stream { ) } - async revokePermission(permissionId) { + async revokePermission(permissionId: Todo) { return authFetch( getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'permissions', permissionId), this._client.session, @@ -111,7 +120,7 @@ export default class Stream { ) } - async addToStorageNode(address) { + async addToStorageNode(address: Todo) { return authFetch( getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'storageNodes'), this._client.session, @@ -124,7 +133,7 @@ export default class Stream { ) } - async removeFromStorageNode(address) { + async removeFromStorageNode(address: Todo) { return authFetch( getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'storageNodes', address), this._client.session, @@ -139,10 +148,10 @@ export default class Stream { getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'storageNodes'), this._client.session, ) - return json.map((item) => new StorageNode(item.storageNodeAddress)) + return json.map((item: Todo) => new StorageNode(item.storageNodeAddress)) } - async publish(...theArgs) { + async publish(...theArgs: Todo) { return this._client.publish(this.id, ...theArgs) } } diff --git a/test/flakey/EnvStressTest.test.js b/test/flakey/EnvStressTest.test.js index f8e5d54f8..d1da5f4d9 100644 --- a/test/flakey/EnvStressTest.test.js +++ b/test/flakey/EnvStressTest.test.js @@ -1,5 +1,5 @@ import { pTimeout } from '../../src/utils' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import { fakePrivateKey, uid } from '../utils' import config from '../integration/config' diff --git a/test/integration/DataUnionEndpoints/DataUnionEndpoints.test.js b/test/integration/DataUnionEndpoints/DataUnionEndpoints.test.js index 1a6f7e504..0a5ab0a21 100644 --- a/test/integration/DataUnionEndpoints/DataUnionEndpoints.test.js +++ b/test/integration/DataUnionEndpoints/DataUnionEndpoints.test.js @@ -5,7 +5,7 @@ import debug from 'debug' import { getEndpointUrl } from '../../../src/utils' import authFetch from '../../../src/rest/authFetch' -import StreamrClient from '../../../src' +import StreamrClient from '../../../src/StreamrClient' import * as Token from '../../../contracts/TestToken.json' import config from '../config' diff --git a/test/integration/DataUnionEndpoints/adminWithdrawMember.test.js b/test/integration/DataUnionEndpoints/adminWithdrawMember.test.js index bc29b2967..f220d6f04 100644 --- a/test/integration/DataUnionEndpoints/adminWithdrawMember.test.js +++ b/test/integration/DataUnionEndpoints/adminWithdrawMember.test.js @@ -3,7 +3,7 @@ import { formatEther, parseEther } from 'ethers/lib/utils' import debug from 'debug' import { getEndpointUrl, until } from '../../../src/utils' -import StreamrClient from '../../../src' +import StreamrClient from '../../../src/StreamrClient' import * as Token from '../../../contracts/TestToken.json' import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' import config from '../config' diff --git a/test/integration/DataUnionEndpoints/adminWithdrawSigned.test.js b/test/integration/DataUnionEndpoints/adminWithdrawSigned.test.js index 9d018e4de..dcb58630d 100644 --- a/test/integration/DataUnionEndpoints/adminWithdrawSigned.test.js +++ b/test/integration/DataUnionEndpoints/adminWithdrawSigned.test.js @@ -3,7 +3,7 @@ import { formatEther, parseEther } from 'ethers/lib/utils' import debug from 'debug' import { getEndpointUrl, until } from '../../../src/utils' -import StreamrClient from '../../../src' +import StreamrClient from '../../../src/StreamrClient' import * as Token from '../../../contracts/TestToken.json' import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' import config from '../config' diff --git a/test/integration/DataUnionEndpoints/calculate.test.js b/test/integration/DataUnionEndpoints/calculate.test.js index 0fcc5e96d..e7d92ca4e 100644 --- a/test/integration/DataUnionEndpoints/calculate.test.js +++ b/test/integration/DataUnionEndpoints/calculate.test.js @@ -1,7 +1,7 @@ import { providers, Wallet } from 'ethers' import debug from 'debug' -import StreamrClient from '../../../src' +import StreamrClient from '../../../src/StreamrClient' import config from '../config' const log = debug('StreamrClient::DataUnionEndpoints::integration-test-calculate') diff --git a/test/integration/DataUnionEndpoints/withdraw.test.js b/test/integration/DataUnionEndpoints/withdraw.test.js index cf7b28624..08d4a0e8d 100644 --- a/test/integration/DataUnionEndpoints/withdraw.test.js +++ b/test/integration/DataUnionEndpoints/withdraw.test.js @@ -3,7 +3,7 @@ import { formatEther, parseEther } from 'ethers/lib/utils' import debug from 'debug' import { getEndpointUrl, until } from '../../../src/utils' -import StreamrClient from '../../../src' +import StreamrClient from '../../../src/StreamrClient' import * as Token from '../../../contracts/TestToken.json' import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' import config from '../config' diff --git a/test/integration/DataUnionEndpoints/withdrawTo.test.js b/test/integration/DataUnionEndpoints/withdrawTo.test.js index bfabe7fed..b7624c9e0 100644 --- a/test/integration/DataUnionEndpoints/withdrawTo.test.js +++ b/test/integration/DataUnionEndpoints/withdrawTo.test.js @@ -3,7 +3,7 @@ import { formatEther, parseEther } from 'ethers/lib/utils' import debug from 'debug' import { getEndpointUrl, until } from '../../../src/utils' -import StreamrClient from '../../../src' +import StreamrClient from '../../../src/StreamrClient' import * as Token from '../../../contracts/TestToken.json' import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' import config from '../config' diff --git a/test/integration/Encryption.test.js b/test/integration/Encryption.test.js index bd0d08341..3ac930bd1 100644 --- a/test/integration/Encryption.test.js +++ b/test/integration/Encryption.test.js @@ -3,7 +3,7 @@ import { MessageLayer } from 'streamr-client-protocol' import { fakePrivateKey, uid, Msg, getPublishTestMessages } from '../utils' import { Defer } from '../../src/utils' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import { GroupKey } from '../../src/stream/Encryption' import Connection from '../../src/Connection' diff --git a/test/integration/GapFill.test.js b/test/integration/GapFill.test.js index c6aee66d4..af2b7e7ec 100644 --- a/test/integration/GapFill.test.js +++ b/test/integration/GapFill.test.js @@ -1,7 +1,7 @@ import { wait } from 'streamr-test-utils' import { uid, fakePrivateKey, describeRepeats, getPublishTestMessages } from '../utils' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import Connection from '../../src/Connection' import config from './config' diff --git a/test/integration/LoginEndpoints.test.js b/test/integration/LoginEndpoints.test.js index 7ceb8213a..9ba86f428 100644 --- a/test/integration/LoginEndpoints.test.js +++ b/test/integration/LoginEndpoints.test.js @@ -2,7 +2,7 @@ import assert from 'assert' import { ethers } from 'ethers' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import config from './config' diff --git a/test/integration/MultipleClients.test.js b/test/integration/MultipleClients.test.js index 62436a04a..56bce9cad 100644 --- a/test/integration/MultipleClients.test.js +++ b/test/integration/MultipleClients.test.js @@ -1,7 +1,7 @@ import { wait } from 'streamr-test-utils' import { describeRepeats, uid, fakePrivateKey, getWaitForStorage, getPublishTestMessages, addAfterFn } from '../utils' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import { counterId } from '../../src/utils' import Connection from '../../src/Connection' diff --git a/test/integration/ResendReconnect.test.js b/test/integration/ResendReconnect.test.js index 22031a99c..dc650c20e 100644 --- a/test/integration/ResendReconnect.test.js +++ b/test/integration/ResendReconnect.test.js @@ -1,7 +1,7 @@ import { wait, waitForCondition } from 'streamr-test-utils' import { uid, fakePrivateKey, getPublishTestMessages } from '../utils' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import { Defer } from '../../src/utils' import config from './config' diff --git a/test/integration/Resends.test.js b/test/integration/Resends.test.js index 6c9914d5b..611dbcf25 100644 --- a/test/integration/Resends.test.js +++ b/test/integration/Resends.test.js @@ -1,7 +1,7 @@ import { wait, waitForCondition, waitForEvent } from 'streamr-test-utils' import { uid, describeRepeats, fakePrivateKey, getPublishTestMessages } from '../utils' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import { Defer, pTimeout } from '../../src/utils' import Connection from '../../src/Connection' diff --git a/test/integration/Sequencing.test.js b/test/integration/Sequencing.test.js index 4c5800d16..b75720d34 100644 --- a/test/integration/Sequencing.test.js +++ b/test/integration/Sequencing.test.js @@ -1,7 +1,7 @@ import { wait, waitForCondition, waitForEvent } from 'streamr-test-utils' import { uid, fakePrivateKey, getWaitForStorage } from '../utils' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import Connection from '../../src/Connection' import config from './config' diff --git a/test/integration/Session.test.js b/test/integration/Session.test.js index d20264505..90c9b0bf7 100644 --- a/test/integration/Session.test.js +++ b/test/integration/Session.test.js @@ -1,4 +1,4 @@ -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import { fakePrivateKey } from '../utils' import config from './config' diff --git a/test/integration/StreamConnectionState.test.js b/test/integration/StreamConnectionState.test.js index 48c324a65..4c137da74 100644 --- a/test/integration/StreamConnectionState.test.js +++ b/test/integration/StreamConnectionState.test.js @@ -2,7 +2,7 @@ import { wait } from 'streamr-test-utils' import { ControlLayer } from 'streamr-client-protocol' import { uid, fakePrivateKey, describeRepeats, getPublishTestMessages } from '../utils' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import { Defer, pLimitFn } from '../../src/utils' import Connection from '../../src/Connection' diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.js index e1be6c464..e72aae728 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.js @@ -1,7 +1,7 @@ import { ethers } from 'ethers' import { wait } from 'streamr-test-utils' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import { uid } from '../utils' import config from './config' diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index edd1b81b4..407977ad7 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -6,7 +6,7 @@ import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import { wait, waitForEvent } from 'streamr-test-utils' import { describeRepeats, uid, fakePrivateKey, getWaitForStorage, getPublishTestMessages, Msg } from '../utils' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import { Defer, pLimitFn } from '../../src/utils' import Connection from '../../src/Connection' diff --git a/test/integration/Subscriber.test.js b/test/integration/Subscriber.test.js index fdc7ac1ea..379390e51 100644 --- a/test/integration/Subscriber.test.js +++ b/test/integration/Subscriber.test.js @@ -2,7 +2,7 @@ import { ControlLayer } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' import { uid, fakePrivateKey, describeRepeats, getPublishTestMessages, collect } from '../utils' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import { Defer } from '../../src/utils' import Connection from '../../src/Connection' diff --git a/test/integration/SubscriberResends.test.js b/test/integration/SubscriberResends.test.js index acda54340..806ba8058 100644 --- a/test/integration/SubscriberResends.test.js +++ b/test/integration/SubscriberResends.test.js @@ -2,7 +2,7 @@ import { ControlLayer } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' import { Msg, uid, collect, describeRepeats, fakePrivateKey, getWaitForStorage, getPublishTestMessages } from '../utils' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import Connection from '../../src/Connection' import { Defer } from '../../src/utils' diff --git a/test/integration/Subscription.test.js b/test/integration/Subscription.test.js index 7f1ee0312..b15dfadca 100644 --- a/test/integration/Subscription.test.js +++ b/test/integration/Subscription.test.js @@ -1,7 +1,7 @@ import { wait, waitForEvent } from 'streamr-test-utils' import { uid, fakePrivateKey } from '../utils' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import config from './config' diff --git a/test/integration/Validation.test.js b/test/integration/Validation.test.js index 6196ee235..54e072497 100644 --- a/test/integration/Validation.test.js +++ b/test/integration/Validation.test.js @@ -1,7 +1,7 @@ import { wait } from 'streamr-test-utils' import { uid, fakePrivateKey, describeRepeats, getPublishTestMessages } from '../utils' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import Connection from '../../src/Connection' import config from './config' diff --git a/test/integration/authFetch.test.js b/test/integration/authFetch.test.js index 1c8749049..bfef4d431 100644 --- a/test/integration/authFetch.test.js +++ b/test/integration/authFetch.test.js @@ -2,7 +2,7 @@ jest.mock('node-fetch') import fetch from 'node-fetch' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import { fakePrivateKey } from '../utils' import config from './config' diff --git a/test/unit/Session.test.js b/test/unit/Session.test.js index 9610f5797..52309eba4 100644 --- a/test/unit/Session.test.js +++ b/test/unit/Session.test.js @@ -1,6 +1,6 @@ import sinon from 'sinon' -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import { Defer } from '../../src/utils' import Session from '../../src/Session' import config from '../integration/config' diff --git a/webpack.config.js b/webpack.config.js index 256000d2d..52b22578e 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -25,7 +25,7 @@ module.exports = (env, argv) => { const commonConfig = { mode: isProduction ? 'production' : 'development', - entry: path.join(__dirname, 'src', 'index.ts'), + entry: path.join(__dirname, 'src', 'StreamrClient.ts'), devtool: 'source-map', output: { path: path.join(__dirname, 'dist'), From 04ef6e8cc906f24652b5b4b03325cdaf972160f6 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 16 Feb 2021 14:42:01 +0200 Subject: [PATCH 426/517] Use explicit parameters in untilWithdrawIsComplete --- src/rest/DataUnionEndpoints.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/rest/DataUnionEndpoints.ts b/src/rest/DataUnionEndpoints.ts index 7daaf158d..e31978c09 100644 --- a/src/rest/DataUnionEndpoints.ts +++ b/src/rest/DataUnionEndpoints.ts @@ -419,7 +419,7 @@ async function transportSignatures(client: StreamrClient, messageHash: Todo, opt // template for withdraw functions // client could be replaced with AMB (mainnet and sidechain) -async function untilWithdrawIsComplete(client: StreamrClient, getWithdrawTxFunc: Todo, getBalanceFunc: Todo, options: Todo = {}) { +async function untilWithdrawIsComplete(client: StreamrClient, getWithdrawTxFunc: (options: Todo) => Todo, getBalanceFunc: (options: Todo) => Todo, options: Todo = {}) { const { pollingIntervalMs = 1000, retryTimeoutMs = 60000, @@ -744,8 +744,8 @@ export class DataUnionEndpoints { const address = getAddress(memberAddress) // throws if bad address const tr = await untilWithdrawIsComplete( this.client, - this.getWithdrawMemberTx.bind(this, address), - this.getTokenBalance.bind(this, address), + (opts) => this.getWithdrawMemberTx(address, opts), + (opts) => this.getTokenBalance(address, opts), { ...this.client.options, ...options } ) return tr @@ -778,8 +778,8 @@ export class DataUnionEndpoints { const to = getAddress(recipientAddress) const tr = await untilWithdrawIsComplete( this.client, - this.getWithdrawToSignedTx.bind(this, from, to, signature), - this.getTokenBalance.bind(this, to), + (opts) => this.getWithdrawToSignedTx(from, to, signature, opts), + (opts) => this.getTokenBalance(to, opts), { ...this.client.options, ...options } ) return tr @@ -1015,8 +1015,8 @@ export class DataUnionEndpoints { async withdraw(options: Todo = {}) { const tr = await untilWithdrawIsComplete( this.client, - this.getWithdrawTx.bind(this), - this.getTokenBalance.bind(this, null), // null means this StreamrClient's auth credentials + (opts) => this.getWithdrawTx(opts), + (opts) => this.getTokenBalance(null, opts), // null means this StreamrClient's auth credentials { ...this.client.options, ...options } ) return tr @@ -1055,8 +1055,8 @@ export class DataUnionEndpoints { const to = getAddress(recipientAddress) // throws if bad address const tr = await untilWithdrawIsComplete( this.client, - this.getWithdrawTxTo.bind(this, to), - this.getTokenBalance.bind(this, to), + (opts) => this.getWithdrawTxTo(to, opts), + (opts) => this.getTokenBalance(to, opts), { ...this.client.options, ...options } ) return tr From 4f9cdf5bf6341c7305c776474b45c28f7449c54b Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 16 Feb 2021 14:42:07 +0200 Subject: [PATCH 427/517] Run DataUnion tests in band: concurrent run causes "Transaction with the same hash was already imported" error --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 417f496b5..ad5edd75e 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "test-integration": "jest --forceExit test/integration", "test-integration-no-resend": "jest --testTimeout=10000 --testPathIgnorePatterns='resend|Resend' --testNamePattern='^((?!(resend|Resend|resent|Resent)).)*$' test/integration/*.test.js", "test-integration-resend": "jest --testTimeout=15000 --testNamePattern='(resend|Resend|resent|Resent)' test/integration/*.test.js", - "test-integration-dataunions": "jest --testTimeout=15000 test/integration/DataUnionEndpoints", + "test-integration-dataunions": "jest --testTimeout=15000 --runInBand test/integration/DataUnionEndpoints", "test-flakey": "jest --forceExit test/flakey/*", "test-browser": "node ./test/browser/server.js & node node_modules/nightwatch/bin/nightwatch ./test/browser/browser.js && pkill -f server.js", "install-example": "cd examples/webpack && npm ci", From afdd50d2fc2d1774f734eb7593e5e1eb9a8b4f3c Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 16 Feb 2021 14:42:54 +0200 Subject: [PATCH 428/517] TypeScript support to ESLint --- .eslintrc.js | 11 ++- package-lock.json | 192 +++++++++++++++++++++++++++++++++++++--------- package.json | 7 +- 3 files changed, 168 insertions(+), 42 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 18ba0b22b..543def847 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,5 +1,8 @@ module.exports = { - parser: 'babel-eslint', + parser: '@typescript-eslint/parser', + plugins: [ + '@typescript-eslint' + ], extends: [ 'streamr-nodejs' ], @@ -35,7 +38,11 @@ module.exports = { 'lines-between-class-members': 'off', 'padded-blocks': 'off', 'no-use-before-define': 'off', - 'import/order': 'off' + 'import/order': 'off', + 'no-shadow': 'off', + '@typescript-eslint/no-shadow': 'error', + 'no-unused-vars': 'off', + '@typescript-eslint/no-unused-vars': ['error'] }, settings: { 'import/resolver': { diff --git a/package-lock.json b/package-lock.json index afacd097c..59d71601e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2599,6 +2599,111 @@ "@types/node": "*" } }, + "@typescript-eslint/eslint-plugin": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.15.1.tgz", + "integrity": "sha512-yW2epMYZSpNJXZy22Biu+fLdTG8Mn6b22kR3TqblVk50HGNV8Zya15WAXuQCr8tKw4Qf1BL4QtI6kv6PCkLoJw==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "4.15.1", + "@typescript-eslint/scope-manager": "4.15.1", + "debug": "^4.1.1", + "functional-red-black-tree": "^1.0.1", + "lodash": "^4.17.15", + "regexpp": "^3.0.0", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/experimental-utils": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.1.tgz", + "integrity": "sha512-9LQRmOzBRI1iOdJorr4jEnQhadxK4c9R2aEAsm7WE/7dq8wkKD1suaV0S/JucTL8QlYUPU1y2yjqg+aGC0IQBQ==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/scope-manager": "4.15.1", + "@typescript-eslint/types": "4.15.1", + "@typescript-eslint/typescript-estree": "4.15.1", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/parser": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.15.1.tgz", + "integrity": "sha512-V8eXYxNJ9QmXi5ETDguB7O9diAXlIyS+e3xzLoP/oVE4WCAjssxLIa0mqCLsCGXulYJUfT+GV70Jv1vHsdKwtA==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "4.15.1", + "@typescript-eslint/types": "4.15.1", + "@typescript-eslint/typescript-estree": "4.15.1", + "debug": "^4.1.1" + } + }, + "@typescript-eslint/scope-manager": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.1.tgz", + "integrity": "sha512-ibQrTFcAm7yG4C1iwpIYK7vDnFg+fKaZVfvyOm3sNsGAerKfwPVFtYft5EbjzByDJ4dj1WD8/34REJfw/9wdVA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.15.1", + "@typescript-eslint/visitor-keys": "4.15.1" + } + }, + "@typescript-eslint/types": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.1.tgz", + "integrity": "sha512-iGsaUyWFyLz0mHfXhX4zO6P7O3sExQpBJ2dgXB0G5g/8PRVfBBsmQIc3r83ranEQTALLR3Vko/fnCIVqmH+mPw==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.1.tgz", + "integrity": "sha512-z8MN3CicTEumrWAEB2e2CcoZa3KP9+SMYLIA2aM49XW3cWIaiVSOAGq30ffR5XHxRirqE90fgLw3e6WmNx5uNw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.15.1", + "@typescript-eslint/visitor-keys": "4.15.1", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.1.tgz", + "integrity": "sha512-tYzaTP9plooRJY8eNlpAewTOqtWW/4ff/5wBjNVaJ0S0wC4Gpq/zDVRTJa5bq2v1pCNQ08xxMCndcvR+h7lMww==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.15.1", + "eslint-visitor-keys": "^2.0.0" + } + }, "@webassemblyjs/ast": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", @@ -3221,20 +3326,6 @@ } } }, - "babel-eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", - "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/traverse": "^7.7.0", - "@babel/types": "^7.7.0", - "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" - } - }, "babel-helper-function-name": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz", @@ -5380,12 +5471,12 @@ } }, "eslint": { - "version": "7.18.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.18.0.tgz", - "integrity": "sha512-fbgTiE8BfUJZuBeq2Yi7J3RB3WGUQ9PNuNbmgi6jt9Iv8qrkxfy19Ds3OpL1Pm7zg3BtTVhvcUZbIRQ0wmSjAQ==", + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.20.0.tgz", + "integrity": "sha512-qGi0CTcOGP2OtCQBgWZlQjcTuP0XkIpYFj25XtRTQSHC+umNnp7UMshr2G8SLsRFYDdAPFeHOsiteadmMH02Yw==", "dev": true, "requires": { - "@babel/code-frame": "^7.0.0", + "@babel/code-frame": "7.12.11", "@eslint/eslintrc": "^0.3.0", "ajv": "^6.10.0", "chalk": "^4.0.0", @@ -5397,7 +5488,7 @@ "eslint-utils": "^2.1.0", "eslint-visitor-keys": "^2.0.0", "espree": "^7.3.1", - "esquery": "^1.2.0", + "esquery": "^1.4.0", "esutils": "^2.0.2", "file-entry-cache": "^6.0.0", "functional-red-black-tree": "^1.0.1", @@ -5464,12 +5555,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", - "dev": true - }, "glob-parent": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", @@ -5766,12 +5851,20 @@ "dev": true, "requires": { "eslint-visitor-keys": "^1.1.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } } }, "eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", + "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", "dev": true }, "espree": { @@ -5783,6 +5876,14 @@ "acorn": "^7.4.0", "acorn-jsx": "^5.3.1", "eslint-visitor-keys": "^1.3.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true + } } }, "esprima": { @@ -5792,9 +5893,9 @@ "dev": true }, "esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", "dev": true, "requires": { "estraverse": "^5.1.0" @@ -6510,9 +6611,9 @@ } }, "flatted": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.0.tgz", - "integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.1.1.tgz", + "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", "dev": true }, "flush-write-stream": { @@ -13267,9 +13368,9 @@ }, "dependencies": { "ajv": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.0.3.tgz", - "integrity": "sha512-R50QRlXSxqXcQP5SvKUrw8VZeypvo12i2IX0EeR5PiZ7bEKeHWgzgo264LDadUsCU42lTJVhFikTqJwNeH34gQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.0.tgz", + "integrity": "sha512-svS9uILze/cXbH0z2myCK2Brqprx/+JJYK5pHicT/GQiBfzzhUVAIT6MwqJg8y4xV/zoGsUeuPuwtoiKSGE15g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -13612,6 +13713,23 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" }, + "tsutils": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz", + "integrity": "sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } + }, "tty-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", diff --git a/package.json b/package.json index ad5edd75e..c86672f1a 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "prepack": "npm run build", "prebuild": "npm run eslint -- --cache", "dev": "webpack --progress --colors --watch --mode=development", - "eslint": "eslint --cache-location=node_modules/.cache/.eslintcache/ . ", + "eslint": "eslint --cache-location=node_modules/.cache/.eslintcache/ '*/**/*.{js,ts}'", "test": "jest --detectOpenHandles", "test-unit": "jest test/unit --detectOpenHandles", "coverage": "jest --coverage", @@ -54,8 +54,9 @@ "@babel/preset-typescript": "^7.12.13", "@types/debug": "^4.1.5", "@types/qs": "^6.9.5", + "@typescript-eslint/eslint-plugin": "^4.15.1", + "@typescript-eslint/parser": "^4.15.1", "async-mutex": "^0.2.6", - "babel-eslint": "^10.1.0", "babel-loader": "^8.2.2", "babel-plugin-add-module-exports": "^1.0.4", "babel-plugin-transform-class-properties": "^6.24.1", @@ -63,7 +64,7 @@ "buffer": "^6.0.3", "chromedriver": "^88.0.0", "core-js": "^3.8.3", - "eslint": "^7.18.0", + "eslint": "^7.20.0", "eslint-config-airbnb": "^18.2.1", "eslint-config-streamr-nodejs": "^1.3.0", "eslint-loader": "^4.0.2", From 852973d0ac636926609b8b229164d3a6866f3e0c Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 16 Feb 2021 15:00:23 +0200 Subject: [PATCH 429/517] Type definitions --- src/{Config.js => Config.ts} | 21 ++- src/{Session.js => Session.ts} | 82 +++++---- src/StreamrClient.ts | 179 +++++++++++++------- src/rest/DataUnionEndpoints.ts | 121 +++++++------ src/rest/LoginEndpoints.ts | 13 +- src/rest/StreamEndpoints.ts | 49 ++++-- src/stream/{StreamPart.js => StreamPart.ts} | 10 +- src/stream/index.ts | 29 +++- src/types.ts | 2 - test/integration/LoginEndpoints.test.js | 18 +- test/integration/StreamEndpoints.test.js | 2 +- test/unit/Session.test.js | 8 +- 12 files changed, 333 insertions(+), 201 deletions(-) rename src/{Config.js => Config.ts} (85%) rename src/{Session.js => Session.ts} (50%) rename src/stream/{StreamPart.js => StreamPart.ts} (62%) diff --git a/src/Config.js b/src/Config.ts similarity index 85% rename from src/Config.js rename to src/Config.ts index 0894bc733..65442c4c5 100644 --- a/src/Config.js +++ b/src/Config.ts @@ -1,16 +1,18 @@ import qs from 'qs' +// @ts-expect-error import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import Debug from 'debug' import { getVersionString, counterId } from './utils' +import { StreamrClientOptions } from './StreamrClient' const { ControlMessage } = ControlLayer const { StreamMessage } = MessageLayer -export default function ClientConfig(opts = {}) { +export default function ClientConfig(opts: StreamrClientOptions = {}) { const { id = counterId('StreamrClient') } = opts - const options = { + const options: StreamrClientOptions = { debug: Debug(id), // Authentication: identity used by this StreamrClient instance auth: {}, // can contain member privateKey or (window.)ethereum @@ -39,15 +41,20 @@ export default function ClientConfig(opts = {}) { // For ethers.js provider params, see https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#provider mainnet: null, // Default to ethers.js default provider settings sidechain: { + // @ts-expect-error url: null, // TODO: add our default public service sidechain node, also find good PoA params below // timeout: // pollingInterval: }, + // @ts-expect-error dataUnion: null, // Give a "default target" of all data union endpoint operations (no need to pass argument every time) tokenAddress: '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', minimumWithdrawTokenWei: '1000000', // Threshold value set in AMB configs, smallest token amount to pass over the bridge + // @ts-expect-error sidechainTokenAddress: null, // TODO // sidechain token + // @ts-expect-error factoryMainnetAddress: null, // TODO // Data Union factory that creates a new Data Union + // @ts-expect-error sidechainAmbAddress: null, // Arbitrary Message-passing Bridge (AMB), see https://github.com/poanetwork/tokenbridge payForSignatureTransport: true, // someone must pay for transporting the withdraw tx to mainnet, either us or bridge operator ...opts, @@ -58,7 +65,7 @@ export default function ClientConfig(opts = {}) { } } - const parts = options.url.split('?') + const parts = options.url!.split('?') if (parts.length === 1) { // there is no query string const controlLayer = `controlLayerVersion=${ControlMessage.LATEST_VERSION}` const messageLayer = `messageLayerVersion=${StreamMessage.LATEST_VERSION}` @@ -78,16 +85,20 @@ export default function ClientConfig(opts = {}) { options.url = `${options.url}&streamrClient=${getVersionString()}` // Backwards compatibility for option 'authKey' => 'apiKey' + // @ts-expect-error if (options.authKey && !options.apiKey) { + // @ts-expect-error options.apiKey = options.authKey } + // @ts-expect-error if (options.apiKey) { + // @ts-expect-error options.auth.apiKey = options.apiKey } - if (options.auth.privateKey && !options.auth.privateKey.startsWith('0x')) { - options.auth.privateKey = `0x${options.auth.privateKey}` + if (options.auth!.privateKey && !options.auth!.privateKey.startsWith('0x')) { + options.auth!.privateKey = `0x${options.auth!.privateKey}` } return options diff --git a/src/Session.js b/src/Session.ts similarity index 50% rename from src/Session.js rename to src/Session.ts index d4c5eaad6..f9522f131 100644 --- a/src/Session.js +++ b/src/Session.ts @@ -1,29 +1,58 @@ import EventEmitter from 'eventemitter3' import { Wallet } from '@ethersproject/wallet' -import { Web3Provider } from '@ethersproject/providers' +import { ExternalProvider, JsonRpcFetchFunc, Web3Provider } from '@ethersproject/providers' +import StreamrClient from './StreamrClient' + +enum State { + LOGGING_OUT = 'logging out', + LOGGED_OUT = 'logged out', + LOGGING_IN ='logging in', + LOGGED_IN = 'logged in', +} + +export interface SessionOptions { + privateKey?: string + ethereum?: ExternalProvider|JsonRpcFetchFunc + apiKey?: string + username?: string + password?: string + sessionToken?: string + unauthenticated?: boolean +} + +export interface TokenObject { + token: string +} export default class Session extends EventEmitter { - constructor(client, options = {}) { + + _client: StreamrClient + options: SessionOptions + state: State + loginFunction: () => Promise + sessionTokenPromise?: Promise + + constructor(client: StreamrClient, options: SessionOptions = {}) { super() this._client = client this.options = { ...options } - this.state = Session.State.LOGGED_OUT + this.state = State.LOGGED_OUT // TODO: move loginFunction to StreamrClient constructor where "auth type" is checked if (typeof this.options.privateKey !== 'undefined') { const wallet = new Wallet(this.options.privateKey) - this.loginFunction = async () => this._client.loginWithChallengeResponse((d) => wallet.signMessage(d), wallet.address) + this.loginFunction = async () => this._client.loginEndpoints.loginWithChallengeResponse((d: string) => wallet.signMessage(d), wallet.address) } else if (typeof this.options.ethereum !== 'undefined') { const provider = new Web3Provider(this.options.ethereum) const signer = provider.getSigner() - this.loginFunction = async () => this._client.loginWithChallengeResponse((d) => signer.signMessage(d), await signer.getAddress()) + this.loginFunction = async () => this._client.loginEndpoints.loginWithChallengeResponse((d: string) => signer.signMessage(d), await signer.getAddress()) } else if (typeof this.options.apiKey !== 'undefined') { - this.loginFunction = async () => this._client.loginWithApiKey(this.options.apiKey) + this.loginFunction = async () => this._client.loginEndpoints.loginWithApiKey(this.options.apiKey!) } else if (typeof this.options.username !== 'undefined' && typeof this.options.password !== 'undefined') { - this.loginFunction = async () => this._client.loginWithUsernamePassword(this.options.username, this.options.password) + this.loginFunction = async () => this._client.loginEndpoints.loginWithUsernamePassword(this.options.username!, this.options.password!) } else { if (!this.options.sessionToken) { this.options.unauthenticated = true @@ -38,7 +67,7 @@ export default class Session extends EventEmitter { return this.options.unauthenticated } - updateState(newState) { + updateState(newState: State) { this.state = newState this.emit(newState) } @@ -52,19 +81,19 @@ export default class Session extends EventEmitter { return undefined } - if (this.state !== Session.State.LOGGING_IN) { - if (this.state === Session.State.LOGGING_OUT) { + if (this.state !== State.LOGGING_IN) { + if (this.state === State.LOGGING_OUT) { this.sessionTokenPromise = new Promise((resolve) => { - this.once(Session.State.LOGGED_OUT, () => resolve(this.getSessionToken(requireNewToken))) + this.once(State.LOGGED_OUT, () => resolve(this.getSessionToken(requireNewToken))) }) } else { - this.updateState(Session.State.LOGGING_IN) - this.sessionTokenPromise = this.loginFunction().then((tokenObj) => { + this.updateState(State.LOGGING_IN) + this.sessionTokenPromise = this.loginFunction().then((tokenObj: TokenObject) => { this.options.sessionToken = tokenObj.token - this.updateState(Session.State.LOGGED_IN) + this.updateState(State.LOGGED_IN) return tokenObj.token - }, (err) => { - this.updateState(Session.State.LOGGED_OUT) + }, (err: Error) => { + this.updateState(State.LOGGED_OUT) throw err }) } @@ -73,31 +102,24 @@ export default class Session extends EventEmitter { } async logout() { - if (this.state === Session.State.LOGGED_OUT) { + if (this.state === State.LOGGED_OUT) { throw new Error('Already logged out!') } - if (this.state === Session.State.LOGGING_OUT) { + if (this.state === State.LOGGING_OUT) { throw new Error('Already logging out!') } - if (this.state === Session.State.LOGGING_IN) { + if (this.state === State.LOGGING_IN) { await new Promise((resolve) => { - this.once(Session.State.LOGGED_IN, () => resolve(this.logout())) + this.once(State.LOGGED_IN, () => resolve(this.logout())) }) return } - this.updateState(Session.State.LOGGING_OUT) - await this._client.logoutEndpoint() + this.updateState(State.LOGGING_OUT) + await this._client.loginEndpoints.logoutEndpoint() this.options.sessionToken = undefined - this.updateState(Session.State.LOGGED_OUT) + this.updateState(State.LOGGED_OUT) } } - -Session.State = { - LOGGING_OUT: 'logging out', - LOGGED_OUT: 'logged out', - LOGGING_IN: 'logging in', - LOGGED_IN: 'logged in', -} diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index 05c4d8424..a4cd0fb71 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -13,26 +13,78 @@ import Publisher from './publish' import Subscriber from './subscribe' import { getUserId } from './user' import { Todo } from './types' -import { StreamEndpoints } from './rest/StreamEndpoints' +import { StreamEndpoints, StreamListQuery } from './rest/StreamEndpoints' import { LoginEndpoints } from './rest/LoginEndpoints' -import { DataUnionEndpoints } from './rest/DataUnionEndpoints' +import { DataUnionEndpoints, DataUnionOptions } from './rest/DataUnionEndpoints' +import { BigNumber } from '@ethersproject/bignumber' +import Stream, { StreamProperties } from './stream' +import { ExternalProvider, JsonRpcFetchFunc } from '@ethersproject/providers' + +export interface StreamrClientOptions { + id?: string + debug?: Debug.Debugger, + auth?: { + privateKey?: string + ethereum?: ExternalProvider|JsonRpcFetchFunc, + apiKey?: string + username?: string + password?: string + } + url?: string + restUrl?: string + streamrNodeAddress?: string + autoConnect?: boolean + autoDisconnect?: boolean + orderMessages?: boolean, + retryResendAfter?: number, + gapFillTimeout?: number, + maxPublishQueueSize?: number, + publishWithSignature?: Todo, + verifySignatures?: Todo, + publisherStoreKeyHistory?: boolean, + groupKeys?: Todo + keyExchange?: Todo + mainnet?: Todo + sidechain?: { + url?: string + }, + dataUnion?: string + tokenAddress?: string, + minimumWithdrawTokenWei?: BigNumber|number|string, + sidechainTokenAddress?: string + factoryMainnetAddress?: string + sidechainAmbAddress?: string + payForSignatureTransport?: boolean + cache?: { + maxSize?: number, + maxAge?: number + } +} + +// TODO get metadata type from streamr-protocol-js project (it doesn't export the type definitions yet) +export type OnMessageCallback = (message: any, metadata: any) => void + +interface MessageEvent { + data: any +} /** * Wrap connection message events with message parsing. */ class StreamrConnection extends Connection { - constructor(...args: Todo) { + // TODO define args type when we convert Connection class to TypeScript + constructor(...args: any) { super(...args) this.on('message', this.onConnectionMessage) } // eslint-disable-next-line class-methods-use-this - parse(messageEvent: Todo) { + parse(messageEvent: MessageEvent) { return ControlLayer.ControlMessage.deserialize(messageEvent.data) } - onConnectionMessage(messageEvent: Todo) { + onConnectionMessage(messageEvent: MessageEvent) { let controlMessage try { controlMessage = this.parse(messageEvent) @@ -54,19 +106,20 @@ class StreamrConnection extends Connection { class StreamrCached { - client: Todo - getStream: Todo - getUserInfo: Todo - isStreamPublisher: Todo - isStreamSubscriber: Todo - getUserId: Todo + client: StreamrClient + // TODO change all "any" types in this class to valid types when CacheAsyncFn is converted to TypeScript + getStream: any + getUserInfo: any + isStreamPublisher: any + isStreamSubscriber: any + getUserId: any constructor(client: StreamrClient) { this.client = client - const cacheOptions = client.options.cache + const cacheOptions: Todo = client.options.cache this.getStream = CacheAsyncFn(client.getStream.bind(client), { ...cacheOptions, - cacheKey([maybeStreamId]: Todo) { + cacheKey([maybeStreamId]: any) { const { streamId } = validateOptions(maybeStreamId) return streamId } @@ -74,7 +127,7 @@ class StreamrCached { this.getUserInfo = CacheAsyncFn(client.getUserInfo.bind(client), cacheOptions) this.isStreamPublisher = CacheAsyncFn(client.isStreamPublisher.bind(client), { ...cacheOptions, - cacheKey([maybeStreamId, ethAddress]: Todo) { + cacheKey([maybeStreamId, ethAddress]: any) { const { streamId } = validateOptions(maybeStreamId) return `${streamId}|${ethAddress}` } @@ -82,7 +135,7 @@ class StreamrCached { this.isStreamSubscriber = CacheAsyncFn(client.isStreamSubscriber.bind(client), { ...cacheOptions, - cacheKey([maybeStreamId, ethAddress]: Todo) { + cacheKey([maybeStreamId, ethAddress]: any) { const { streamId } = validateOptions(maybeStreamId) return `${streamId}|${ethAddress}` } @@ -91,10 +144,10 @@ class StreamrCached { this.getUserId = CacheAsyncFn(client.getUserId.bind(client), cacheOptions) } - clearStream(streamId: Todo) { + clearStream(streamId: string) { this.getStream.clear() - this.isStreamPublisher.clearMatching((s: Todo) => s.startsWith(streamId)) - this.isStreamSubscriber.clearMatching((s: Todo) => s.startsWith(streamId)) + this.isStreamPublisher.clearMatching((s: string) => s.startsWith(streamId)) + this.isStreamSubscriber.clearMatching((s: string) => s.startsWith(streamId)) } clearUser() { @@ -116,7 +169,7 @@ export default class StreamrClient extends EventEmitter { id: string debug: Debug.Debugger - options: Todo + options: StreamrClientOptions session: Session connection: StreamrConnection publisher: Todo @@ -127,7 +180,7 @@ export default class StreamrClient extends EventEmitter { loginEndpoints: LoginEndpoints dataUnionEndpoints: DataUnionEndpoints - constructor(options: Todo = {}, connection?: StreamrConnection) { + constructor(options: StreamrClientOptions = {}, connection?: StreamrConnection) { super() this.id = counterId(`${this.constructor.name}:${uid}`) this.debug = Debug(this.id) @@ -284,7 +337,7 @@ export default class StreamrClient extends EventEmitter { return this.publisher.rotateGroupKey(...args) } - async subscribe(opts: Todo, onMessage: Todo) { + async subscribe(opts: Todo, onMessage: OnMessageCallback) { let subTask: Todo let sub: Todo const hasResend = !!(opts.resend || opts.from || opts.to || opts.last) @@ -319,7 +372,7 @@ export default class StreamrClient extends EventEmitter { await this.subscriber.unsubscribe(opts) } - async resend(opts: Todo, onMessage: Todo) { + async resend(opts: Todo, onMessage: OnMessageCallback) { const task = this.subscriber.resend(opts) if (typeof onMessage !== 'function') { return task @@ -360,11 +413,11 @@ export default class StreamrClient extends EventEmitter { // TODO many of these methods that use streamEndpoints/loginEndpoints/dataUnionEndpoints are private: remove those - async getStream(streamId: Todo) { + async getStream(streamId: string) { return this.streamEndpoints.getStream(streamId) } - async listStreams(query: Todo = {}) { + async listStreams(query: StreamListQuery = {}) { return this.streamEndpoints.listStreams(query) } @@ -372,46 +425,46 @@ export default class StreamrClient extends EventEmitter { return this.streamEndpoints.getStreamByName(name) } - async createStream(props: Todo) { + async createStream(props: StreamProperties) { return this.streamEndpoints.createStream(props) } - async getOrCreateStream(props: Todo) { + async getOrCreateStream(props: { id?: string, name?: string }) { return this.streamEndpoints.getOrCreateStream(props) } - async getStreamPublishers(streamId: Todo) { + async getStreamPublishers(streamId: string) { return this.streamEndpoints.getStreamPublishers(streamId) } - async isStreamPublisher(streamId: Todo, ethAddress: Todo) { + async isStreamPublisher(streamId: string, ethAddress: string) { return this.streamEndpoints.isStreamPublisher(streamId, ethAddress) } - async getStreamSubscribers(streamId: Todo) { + async getStreamSubscribers(streamId: string) { return this.streamEndpoints.getStreamSubscribers(streamId) } - async isStreamSubscriber(streamId: Todo, ethAddress: Todo) { + async isStreamSubscriber(streamId: string, ethAddress: string) { return this.streamEndpoints.isStreamSubscriber(streamId, ethAddress) } - async getStreamValidationInfo(streamId: Todo) { + async getStreamValidationInfo(streamId: string) { return this.streamEndpoints.getStreamValidationInfo(streamId) } - async getStreamLast(streamObjectOrId: Todo) { + async getStreamLast(streamObjectOrId: Stream|string) { return this.streamEndpoints.getStreamLast(streamObjectOrId) } - async getStreamPartsByStorageNode(address: Todo) { + async getStreamPartsByStorageNode(address: string) { return this.streamEndpoints.getStreamPartsByStorageNode(address) } - async publishHttp(streamObjectOrId: Todo, data: Todo, requestOptions: Todo = {}, keepAlive: Todo = true) { + async publishHttp(streamObjectOrId: Stream|string, data: Todo, requestOptions: Todo = {}, keepAlive: boolean = true) { return this.streamEndpoints.publishHttp(streamObjectOrId, data, requestOptions, keepAlive) } - + async getChallenge(address: Todo) { return this.loginEndpoints.getChallenge(address) } @@ -440,115 +493,115 @@ export default class StreamrClient extends EventEmitter { return this.loginEndpoints.logoutEndpoint() } - async calculateDataUnionMainnetAddress(dataUnionName: Todo, deployerAddress: Todo, options: Todo) { + async calculateDataUnionMainnetAddress(dataUnionName: string, deployerAddress: string, options: DataUnionOptions) { return this.dataUnionEndpoints.calculateDataUnionMainnetAddress(dataUnionName, deployerAddress, options) } - async calculateDataUnionSidechainAddress(duMainnetAddress: Todo, options: Todo) { + async calculateDataUnionSidechainAddress(duMainnetAddress: string, options: DataUnionOptions) { return this.dataUnionEndpoints.calculateDataUnionSidechainAddress(duMainnetAddress, options) } - async deployDataUnion(options: Todo = {}) { + async deployDataUnion(options: DataUnionOptions = {}) { return this.dataUnionEndpoints.deployDataUnion(options) } - async getDataUnionContract(options: Todo = {}) { + async getDataUnionContract(options: DataUnionOptions = {}) { return this.dataUnionEndpoints.getDataUnionContract(options) } - async createSecret(dataUnionMainnetAddress: Todo, name: string = 'Untitled Data Union Secret') { + async createSecret(dataUnionMainnetAddress: string, name: string = 'Untitled Data Union Secret') { return this.dataUnionEndpoints.createSecret(dataUnionMainnetAddress, name) } - async kick(memberAddressList: Todo, options: Todo = {}) { + async kick(memberAddressList: string[], options: DataUnionOptions = {}) { return this.dataUnionEndpoints.kick(memberAddressList, options) } - async addMembers(memberAddressList: Todo, options: Todo = {}) { + async addMembers(memberAddressList: string[], options: DataUnionOptions = {}) { return this.dataUnionEndpoints.addMembers(memberAddressList, options) } - async withdrawMember(memberAddress: Todo, options: Todo) { + async withdrawMember(memberAddress: string, options: DataUnionOptions) { return this.dataUnionEndpoints.withdrawMember(memberAddress, options) } - async getWithdrawMemberTx(memberAddress: Todo, options: Todo) { + async getWithdrawMemberTx(memberAddress: string, options: DataUnionOptions) { return this.dataUnionEndpoints.getWithdrawMemberTx(memberAddress, options) } - async withdrawToSigned(memberAddress: Todo, recipientAddress: Todo, signature: Todo, options: Todo) { + async withdrawToSigned(memberAddress: string, recipientAddress: string, signature: string, options: DataUnionOptions) { return this.dataUnionEndpoints.withdrawToSigned(memberAddress, recipientAddress, signature, options) } - async getWithdrawToSignedTx(memberAddress: Todo, recipientAddress: Todo, signature: Todo, options: Todo) { + async getWithdrawToSignedTx(memberAddress: string, recipientAddress: string, signature: string, options: DataUnionOptions) { return this.dataUnionEndpoints.getWithdrawToSignedTx(memberAddress, recipientAddress, signature, options) } - async setAdminFee(newFeeFraction: Todo, options: Todo) { + async setAdminFee(newFeeFraction: number, options: DataUnionOptions) { return this.dataUnionEndpoints.setAdminFee(newFeeFraction, options) } - async getAdminFee(options: Todo) { + async getAdminFee(options: DataUnionOptions) { return this.dataUnionEndpoints.getAdminFee(options) } - async getAdminAddress(options: Todo) { + async getAdminAddress(options: DataUnionOptions) { return this.dataUnionEndpoints.getAdminAddress(options) } - async joinDataUnion(options: Todo = {}) { + async joinDataUnion(options: DataUnionOptions = {}) { return this.dataUnionEndpoints.joinDataUnion(options) } - async hasJoined(memberAddress: Todo, options: Todo = {}) { + async hasJoined(memberAddress: string, options: DataUnionOptions = {}) { return this.dataUnionEndpoints.hasJoined(memberAddress, options) } - async getMembers(options: Todo) { + async getMembers(options: DataUnionOptions) { return this.dataUnionEndpoints.getMembers(options) } - async getDataUnionStats(options: Todo) { + async getDataUnionStats(options: DataUnionOptions) { return this.dataUnionEndpoints.getDataUnionStats(options) } - async getMemberStats(memberAddress: Todo, options: Todo) { + async getMemberStats(memberAddress: string, options: DataUnionOptions) { return this.dataUnionEndpoints.getMemberStats(memberAddress, options) } - async getMemberBalance(memberAddress: Todo, options: Todo) { + async getMemberBalance(memberAddress: string, options: DataUnionOptions) { return this.dataUnionEndpoints.getMemberBalance(memberAddress, options) } - async getTokenBalance(address: Todo, options: Todo) { + async getTokenBalance(address: string|null|undefined, options: DataUnionOptions) { return this.dataUnionEndpoints.getTokenBalance(address, options) } - async getDataUnionVersion(contractAddress: Todo) { + async getDataUnionVersion(contractAddress: string) { return this.dataUnionEndpoints.getDataUnionVersion(contractAddress) } - async withdraw(options: Todo = {}) { + async withdraw(options: DataUnionOptions = {}) { return this.dataUnionEndpoints.withdraw(options) } - async getWithdrawTx(options: Todo) { + async getWithdrawTx(options: DataUnionOptions) { return this.dataUnionEndpoints.getWithdrawTx(options) } - async withdrawTo(recipientAddress: Todo, options = {}) { + async withdrawTo(recipientAddress: string, options: DataUnionOptions = {}) { return this.dataUnionEndpoints.withdrawTo(recipientAddress, options) } - async getWithdrawTxTo(recipientAddress: Todo, options: Todo) { + async getWithdrawTxTo(recipientAddress: string, options: DataUnionOptions) { return this.dataUnionEndpoints.getWithdrawTxTo(recipientAddress, options) } - async signWithdrawTo(recipientAddress: Todo, options: Todo) { + async signWithdrawTo(recipientAddress: string, options: DataUnionOptions) { return this.dataUnionEndpoints.signWithdrawTo(recipientAddress, options) } - async signWithdrawAmountTo(recipientAddress: Todo, amountTokenWei: Todo, options: Todo) { + async signWithdrawAmountTo(recipientAddress: string, amountTokenWei: BigNumber|number|string, options: DataUnionOptions) { return this.dataUnionEndpoints.signWithdrawAmountTo(recipientAddress, amountTokenWei, options) } } diff --git a/src/rest/DataUnionEndpoints.ts b/src/rest/DataUnionEndpoints.ts index e31978c09..697a2347c 100644 --- a/src/rest/DataUnionEndpoints.ts +++ b/src/rest/DataUnionEndpoints.ts @@ -24,6 +24,20 @@ import { until, getEndpointUrl } from '../utils' import authFetch from './authFetch' +export interface DataUnionOptions { + wallet?: Todo, + provider?: Todo, + confirmations?: Todo, + gasPrice?: Todo, + dataUnion?: Todo, + tokenAddress?: Todo, + minimumWithdrawTokenWei?: BigNumber|number|string, + sidechainTokenAddress?: string, + factoryMainnetAddress?: string, + sidechainAmbAddress?: string, + payForSignatureTransport?: boolean +} + const log = debug('StreamrClient::DataUnionEndpoints') // const log = console.log // useful for debugging sometimes @@ -261,7 +275,7 @@ const sidechainAmbABI = [{ /** @typedef {String} EthereumAddress */ -function throwIfBadAddress(address: Todo, variableDescription: Todo) { +function throwIfBadAddress(address: string, variableDescription: Todo) { try { return getAddress(address) } catch (e) { @@ -275,8 +289,8 @@ function throwIfBadAddress(address: Todo, variableDescription: Todo) { * @param {EthereumAddress} inputAddress from user (NOT case sensitive) * @returns {EthereumAddress} with checksum case */ -function parseAddress(client: StreamrClient, inputAddress: Todo) { - if (isAddress(inputAddress)) { +function parseAddress(client: StreamrClient, inputAddress: string|null|undefined) { + if (inputAddress && isAddress(inputAddress)) { return getAddress(inputAddress) } return client.getAddress() @@ -284,12 +298,12 @@ function parseAddress(client: StreamrClient, inputAddress: Todo) { // Find the Asyncronous Message-passing Bridge sidechain ("home") contract let cachedSidechainAmb: Todo -async function getSidechainAmb(client: StreamrClient, options: Todo) { +async function getSidechainAmb(client: StreamrClient, options: DataUnionOptions) { if (!cachedSidechainAmb) { const getAmbPromise = async () => { const mainnetProvider = client.ethereum.getMainnetProvider() const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress - const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, mainnetProvider) + const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, mainnetProvider) const sidechainProvider = client.ethereum.getSidechainProvider() const factorySidechainAddress = await factoryMainnet.data_union_sidechain_factory() const factorySidechain = new Contract(factorySidechainAddress, [{ @@ -310,15 +324,15 @@ async function getSidechainAmb(client: StreamrClient, options: Todo) { return cachedSidechainAmb } -async function getMainnetAmb(client: StreamrClient, options: Todo) { +async function getMainnetAmb(client: StreamrClient, options: DataUnionOptions) { const mainnetProvider = client.ethereum.getMainnetProvider() const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress - const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, mainnetProvider) + const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, mainnetProvider) const mainnetAmbAddress = await factoryMainnet.amb() return new Contract(mainnetAmbAddress, mainnetAmbABI, mainnetProvider) } -async function requiredSignaturesHaveBeenCollected(client: StreamrClient, messageHash: Todo, options: Todo = {}) { +async function requiredSignaturesHaveBeenCollected(client: StreamrClient, messageHash: Todo, options: DataUnionOptions = {}) { const sidechainAmb = await getSidechainAmb(client, options) const requiredSignatureCount = await sidechainAmb.requiredSignatures() @@ -333,7 +347,7 @@ async function requiredSignaturesHaveBeenCollected(client: StreamrClient, messag } // move signatures from sidechain to mainnet -async function transportSignatures(client: StreamrClient, messageHash: Todo, options: Todo) { +async function transportSignatures(client: StreamrClient, messageHash: Todo, options: DataUnionOptions) { const sidechainAmb = await getSidechainAmb(client, options) const message = await sidechainAmb.message(messageHash) const messageId = '0x' + message.substr(2, 64) @@ -419,11 +433,11 @@ async function transportSignatures(client: StreamrClient, messageHash: Todo, opt // template for withdraw functions // client could be replaced with AMB (mainnet and sidechain) -async function untilWithdrawIsComplete(client: StreamrClient, getWithdrawTxFunc: (options: Todo) => Todo, getBalanceFunc: (options: Todo) => Todo, options: Todo = {}) { +async function untilWithdrawIsComplete(client: StreamrClient, getWithdrawTxFunc: (options: DataUnionOptions) => Todo, getBalanceFunc: (options: DataUnionOptions) => Todo, options: DataUnionOptions = {}) { const { pollingIntervalMs = 1000, retryTimeoutMs = 60000, - } = options + }: Todo = options const balanceBefore = await getBalanceFunc(options) const tx = await getWithdrawTxFunc(options) const tr = await tx.wait() @@ -473,11 +487,11 @@ async function untilWithdrawIsComplete(client: StreamrClient, getWithdrawTxFunc: // key the cache with name only, since PROBABLY one StreamrClient will ever use only one private key const mainnetAddressCache: Todo = {} // mapping: "name" -> mainnet address /** @returns {Promise} Mainnet address for Data Union */ -async function getDataUnionMainnetAddress(client: StreamrClient, dataUnionName: Todo, deployerAddress: Todo, options: Todo = {}) { +async function getDataUnionMainnetAddress(client: StreamrClient, dataUnionName: string, deployerAddress: string, options: DataUnionOptions = {}) { if (!mainnetAddressCache[dataUnionName]) { const provider = client.ethereum.getMainnetProvider() const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress - const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, provider) + const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, provider) const addressPromise = factoryMainnet.mainnetAddress(deployerAddress, dataUnionName) mainnetAddressCache[dataUnionName] = addressPromise mainnetAddressCache[dataUnionName] = await addressPromise // eslint-disable-line require-atomic-updates @@ -488,11 +502,11 @@ async function getDataUnionMainnetAddress(client: StreamrClient, dataUnionName: // TODO: calculate addresses in JS const sidechainAddressCache: Todo = {} // mapping: mainnet address -> sidechain address /** @returns {Promise} Sidechain address for Data Union */ -async function getDataUnionSidechainAddress(client: StreamrClient, duMainnetAddress: Todo, options: Todo = {}) { +async function getDataUnionSidechainAddress(client: StreamrClient, duMainnetAddress: string, options: DataUnionOptions = {}) { if (!sidechainAddressCache[duMainnetAddress]) { const provider = client.ethereum.getMainnetProvider() const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress - const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, provider) + const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, provider) const addressPromise = factoryMainnet.sidechainAddress(duMainnetAddress) sidechainAddressCache[duMainnetAddress] = addressPromise sidechainAddressCache[duMainnetAddress] = await addressPromise // eslint-disable-line require-atomic-updates @@ -500,7 +514,8 @@ async function getDataUnionSidechainAddress(client: StreamrClient, duMainnetAddr return sidechainAddressCache[duMainnetAddress] } -function getMainnetContractReadOnly(client: StreamrClient, options: Todo = {}) { +function getMainnetContractReadOnly(client: StreamrClient, options: DataUnionOptions = {}) { + // @ts-expect-error let dataUnion = options.dataUnion || options.dataUnionAddress || client.options.dataUnion if (isAddress(dataUnion)) { const provider = client.ethereum.getMainnetProvider() @@ -513,14 +528,14 @@ function getMainnetContractReadOnly(client: StreamrClient, options: Todo = {}) { return dataUnion } -function getMainnetContract(client: StreamrClient, options: Todo = {}) { +function getMainnetContract(client: StreamrClient, options: DataUnionOptions = {}) { const du = getMainnetContractReadOnly(client, options) const signer = client.ethereum.getSigner() // @ts-expect-error return du.connect(signer) } -async function getSidechainContract(client: StreamrClient, options: Todo = {}) { +async function getSidechainContract(client: StreamrClient, options: DataUnionOptions = {}) { const signer = await client.ethereum.getSidechainSigner() const duMainnet = getMainnetContractReadOnly(client, options) const duSidechainAddress = await getDataUnionSidechainAddress(client, duMainnet.address, options) @@ -529,7 +544,7 @@ async function getSidechainContract(client: StreamrClient, options: Todo = {}) { return duSidechain } -async function getSidechainContractReadOnly(client: StreamrClient, options: Todo = {}) { +async function getSidechainContractReadOnly(client: StreamrClient, options: DataUnionOptions = {}) { const provider = await client.ethereum.getSidechainProvider() const duMainnet = getMainnetContractReadOnly(client, options) const duSidechainAddress = await getDataUnionSidechainAddress(client, duMainnet.address, options) @@ -550,12 +565,12 @@ export class DataUnionEndpoints { // admin: DEPLOY AND SETUP DATA UNION // ////////////////////////////////////////////////////////////////// - async calculateDataUnionMainnetAddress(dataUnionName: Todo, deployerAddress: Todo, options: Todo) { + async calculateDataUnionMainnetAddress(dataUnionName: string, deployerAddress: string, options: DataUnionOptions) { const address = getAddress(deployerAddress) // throws if bad address return getDataUnionMainnetAddress(this.client, dataUnionName, address, options) } - async calculateDataUnionSidechainAddress(duMainnetAddress: Todo, options: Todo) { + async calculateDataUnionSidechainAddress(duMainnetAddress: string, options: DataUnionOptions) { const address = getAddress(duMainnetAddress) // throws if bad address return getDataUnionSidechainAddress(this.client, address, options) } @@ -590,7 +605,7 @@ export class DataUnionEndpoints { * @param {DeployOptions} options such as adminFee (default: 0) * @return {Promise} that resolves when the new DU is deployed over the bridge to side-chain */ - async deployDataUnion(options: Todo = {}) { + async deployDataUnion(options: DataUnionOptions = {}) { const { owner, joinPartAgents, @@ -598,7 +613,7 @@ export class DataUnionEndpoints { adminFee = 0, sidechainPollingIntervalMs = 1000, sidechainRetryTimeoutMs = 600000, - } = options + }: Todo = options let duName = dataUnionName if (!duName) { @@ -628,6 +643,7 @@ export class DataUnionEndpoints { } } + // @ts-expect-error const duMainnetAddress = await getDataUnionMainnetAddress(this.client, duName, ownerAddress, options) const duSidechainAddress = await getDataUnionSidechainAddress(this.client, duMainnetAddress, options) @@ -636,7 +652,7 @@ export class DataUnionEndpoints { } const factoryMainnetAddress = throwIfBadAddress( - options.factoryMainnetAddress || this.client.options.factoryMainnetAddress, + (options.factoryMainnetAddress || this.client.options.factoryMainnetAddress)!, 'StreamrClient.options.factoryMainnetAddress' ) if (await mainnetProvider.getCode(factoryMainnetAddress) === '0x') { @@ -645,7 +661,7 @@ export class DataUnionEndpoints { // function deployNewDataUnion(address owner, uint256 adminFeeFraction, address[] agents, string duName) // @ts-expect-error - const factoryMainnet = new Contract(factoryMainnetAddress, factoryMainnetABI, mainnetWallet) + const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, mainnetWallet) const tx = await factoryMainnet.deployNewDataUnion( ownerAddress, adminFeeBN, @@ -671,7 +687,7 @@ export class DataUnionEndpoints { return dataUnion } - async getDataUnionContract(options: Todo = {}) { + async getDataUnionContract(options: DataUnionOptions = {}) { const ret = getMainnetContract(this.client, options) // @ts-expect-error ret.sidechain = await getSidechainContract(this.client, options) @@ -684,7 +700,7 @@ export class DataUnionEndpoints { * @param {String} name describes the secret * @returns {String} the server-generated secret */ - async createSecret(dataUnionMainnetAddress: Todo, name: string = 'Untitled Data Union Secret') { + async createSecret(dataUnionMainnetAddress: string, name: string = 'Untitled Data Union Secret') { const duAddress = getAddress(dataUnionMainnetAddress) // throws if bad address const url = getEndpointUrl(this.client.options.restUrl, 'dataunions', duAddress, 'secrets') const res = await authFetch( @@ -712,7 +728,7 @@ export class DataUnionEndpoints { * @param {List} memberAddressList to kick * @returns {Promise} partMembers sidechain transaction */ - async kick(memberAddressList: Todo, options: Todo = {}) { + async kick(memberAddressList: string[], options: DataUnionOptions = {}) { const members = memberAddressList.map(getAddress) // throws if there are bad addresses const duSidechain = await getSidechainContract(this.client, options) const tx = await duSidechain.partMembers(members) @@ -725,7 +741,7 @@ export class DataUnionEndpoints { * @param {List} memberAddressList to add * @returns {Promise} addMembers sidechain transaction */ - async addMembers(memberAddressList: Todo, options: Todo = {}) { + async addMembers(memberAddressList: string[], options: DataUnionOptions = {}) { const members = memberAddressList.map(getAddress) // throws if there are bad addresses const duSidechain = await getSidechainContract(this.client, options) const tx = await duSidechain.addMembers(members) @@ -740,7 +756,7 @@ export class DataUnionEndpoints { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} get receipt once withdraw transaction is confirmed */ - async withdrawMember(memberAddress: Todo, options: Todo) { + async withdrawMember(memberAddress: string, options: DataUnionOptions) { const address = getAddress(memberAddress) // throws if bad address const tr = await untilWithdrawIsComplete( this.client, @@ -758,7 +774,7 @@ export class DataUnionEndpoints { * @param {EthereumOptions} options * @returns {Promise} await on call .wait to actually send the tx */ - async getWithdrawMemberTx(memberAddress: Todo, options: Todo) { + async getWithdrawMemberTx(memberAddress: string, options: DataUnionOptions) { const a = getAddress(memberAddress) // throws if bad address const duSidechain = await getSidechainContract(this.client, options) return duSidechain.withdrawAll(a, true) // sendToMainnet=true @@ -773,7 +789,7 @@ export class DataUnionEndpoints { * @param {EthereumOptions} options * @returns {Promise} get receipt once withdraw transaction is confirmed */ - async withdrawToSigned(memberAddress: Todo, recipientAddress: Todo, signature: Todo, options: Todo) { + async withdrawToSigned(memberAddress: string, recipientAddress: string, signature: string, options: DataUnionOptions) { const from = getAddress(memberAddress) // throws if bad address const to = getAddress(recipientAddress) const tr = await untilWithdrawIsComplete( @@ -794,7 +810,7 @@ export class DataUnionEndpoints { * @param {EthereumOptions} options * @returns {Promise} await on call .wait to actually send the tx */ - async getWithdrawToSignedTx(memberAddress: Todo, recipientAddress: Todo, signature: Todo, options: Todo) { + async getWithdrawToSignedTx(memberAddress: string, recipientAddress: string, signature: string, options: DataUnionOptions) { const duSidechain = await getSidechainContract(this.client, options) return duSidechain.withdrawAllToSigned(memberAddress, recipientAddress, true, signature) // sendToMainnet=true } @@ -804,7 +820,7 @@ export class DataUnionEndpoints { * @param {number} newFeeFraction between 0.0 and 1.0 * @param {EthereumOptions} options */ - async setAdminFee(newFeeFraction: Todo, options: Todo) { + async setAdminFee(newFeeFraction: number, options: DataUnionOptions) { if (newFeeFraction < 0 || newFeeFraction > 1) { throw new Error('newFeeFraction argument must be a number between 0...1, got: ' + newFeeFraction) } @@ -818,13 +834,13 @@ export class DataUnionEndpoints { * Get data union admin fee fraction that admin gets from each revenue event * @returns {number} between 0.0 and 1.0 */ - async getAdminFee(options: Todo) { + async getAdminFee(options: DataUnionOptions) { const duMainnet = getMainnetContractReadOnly(this.client, options) const adminFeeBN = await duMainnet.adminFeeFraction() return +adminFeeBN.toString() / 1e18 } - async getAdminAddress(options: Todo) { + async getAdminAddress(options: DataUnionOptions) { const duMainnet = getMainnetContractReadOnly(this.client, options) return duMainnet.owner() } @@ -842,11 +858,11 @@ export class DataUnionEndpoints { * @property {String} member Ethereum mainnet address of the joining member. If not given, use StreamrClient authentication key * @property {String} secret if given, and correct, join the data union immediately */ - async joinDataUnion(options: Todo = {}) { + async joinDataUnion(options: DataUnionOptions = {}) { const { member, secret, - } = options + }: Todo = options const dataUnion = getMainnetContractReadOnly(this.client, options) const body = { @@ -876,11 +892,11 @@ export class DataUnionEndpoints { * @param {Number} retryTimeoutMs (optional, default: 60000) give up * @return {Promise} resolves when member is in the data union (or fails with HTTP error) */ - async hasJoined(memberAddress: Todo, options: Todo = {}) { + async hasJoined(memberAddress: string, options: DataUnionOptions = {}) { const { pollingIntervalMs = 1000, retryTimeoutMs = 60000, - } = options + }: Todo = options const address = parseAddress(this.client, memberAddress) const duSidechain = await getSidechainContractReadOnly(this.client, options) @@ -889,14 +905,14 @@ export class DataUnionEndpoints { } // TODO: this needs more thought: probably something like getEvents from sidechain? Heavy on RPC? - async getMembers(options: Todo) { + async getMembers(options: DataUnionOptions) { const duSidechain = await getSidechainContractReadOnly(this.client, options) throw new Error(`Not implemented for side-chain data union (at ${duSidechain.address})`) // event MemberJoined(address indexed); // event MemberParted(address indexed); } - async getDataUnionStats(options: Todo) { + async getDataUnionStats(options: DataUnionOptions) { const duSidechain = await getSidechainContractReadOnly(this.client, options) const [ totalEarnings, @@ -922,7 +938,7 @@ export class DataUnionEndpoints { * @param {EthereumAddress} dataUnion to query * @param {EthereumAddress} memberAddress (optional) if not supplied, get the stats of currently logged in StreamrClient (if auth: privateKey) */ - async getMemberStats(memberAddress: Todo, options: Todo) { + async getMemberStats(memberAddress: string, options: DataUnionOptions) { const address = parseAddress(this.client, memberAddress) // TODO: use duSidechain.getMemberStats(address) once it's implemented, to ensure atomic read // (so that memberData is from same block as getEarnings, otherwise withdrawable will be foobar) @@ -946,7 +962,7 @@ export class DataUnionEndpoints { * @param memberAddress whose balance is returned * @return {Promise} */ - async getMemberBalance(memberAddress: Todo, options: Todo) { + async getMemberBalance(memberAddress: string, options: DataUnionOptions) { const address = parseAddress(this.client, memberAddress) const duSidechain = await getSidechainContractReadOnly(this.client, options) return duSidechain.getWithdrawableEarnings(address) @@ -960,7 +976,7 @@ export class DataUnionEndpoints { * was given in StreamrClient constructor. * @returns {Promise} token balance in "wei" (10^-18 parts) */ - async getTokenBalance(address: Todo, options: Todo) { + async getTokenBalance(address: string|null|undefined, options: DataUnionOptions) { const a = parseAddress(this.client, address) const tokenAddressMainnet = options.tokenAddress || ( await getMainnetContractReadOnly(this.client, options).then((c: Todo) => c.token()).catch(() => null) || this.client.options.tokenAddress @@ -985,7 +1001,7 @@ export class DataUnionEndpoints { * @param {EthereumAddress} contractAddress * @returns {number} 1 for old, 2 for current, zero for "not a data union" */ - async getDataUnionVersion(contractAddress: Todo) { + async getDataUnionVersion(contractAddress: string) { const a = getAddress(contractAddress) // throws if bad address const provider = this.client.ethereum.getMainnetProvider() const du = new Contract(a, [{ @@ -1012,7 +1028,7 @@ export class DataUnionEndpoints { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) */ - async withdraw(options: Todo = {}) { + async withdraw(options: DataUnionOptions = {}) { const tr = await untilWithdrawIsComplete( this.client, (opts) => this.getWithdrawTx(opts), @@ -1027,7 +1043,7 @@ export class DataUnionEndpoints { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} await on call .wait to actually send the tx */ - async getWithdrawTx(options: Todo) { + async getWithdrawTx(options: DataUnionOptions) { const signer = await this.client.ethereum.getSidechainSigner() // @ts-expect-error const address = await signer.getAddress() @@ -1051,7 +1067,7 @@ export class DataUnionEndpoints { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) */ - async withdrawTo(recipientAddress: Todo, options = {}) { + async withdrawTo(recipientAddress: string, options: DataUnionOptions = {}) { const to = getAddress(recipientAddress) // throws if bad address const tr = await untilWithdrawIsComplete( this.client, @@ -1068,7 +1084,7 @@ export class DataUnionEndpoints { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} await on call .wait to actually send the tx */ - async getWithdrawTxTo(recipientAddress: Todo, options: Todo) { + async getWithdrawTxTo(recipientAddress: string, options: DataUnionOptions) { const signer = await this.client.ethereum.getSidechainSigner() // @ts-expect-error const address = await signer.getAddress() @@ -1094,7 +1110,7 @@ export class DataUnionEndpoints { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {string} signature authorizing withdrawing all earnings to given recipientAddress */ - async signWithdrawTo(recipientAddress: Todo, options: Todo) { + async signWithdrawTo(recipientAddress: string, options: DataUnionOptions) { return this.signWithdrawAmountTo(recipientAddress, BigNumber.from(0), options) } @@ -1107,7 +1123,7 @@ export class DataUnionEndpoints { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {string} signature authorizing withdrawing all earnings to given recipientAddress */ - async signWithdrawAmountTo(recipientAddress: Todo, amountTokenWei: Todo, options: Todo) { + async signWithdrawAmountTo(recipientAddress: string, amountTokenWei: BigNumber|number|string, options: DataUnionOptions) { const to = getAddress(recipientAddress) // throws if bad address const signer = this.client.ethereum.getSigner() // it shouldn't matter if it's mainnet or sidechain signer since key should be the same // @ts-expect-error @@ -1116,6 +1132,7 @@ export class DataUnionEndpoints { const memberData = await duSidechain.memberData(address) if (memberData[0] === '0') { throw new Error(`${address} is not a member in Data Union (sidechain address ${duSidechain.address})`) } const withdrawn = memberData[3] + // @ts-expect-error const message = to + hexZeroPad(amountTokenWei, 32).slice(2) + duSidechain.address.slice(2) + hexZeroPad(withdrawn, 32).slice(2) // @ts-expect-error const signature = await signer.signMessage(arrayify(message)) diff --git a/src/rest/LoginEndpoints.ts b/src/rest/LoginEndpoints.ts index 910c3b3d2..af281830f 100644 --- a/src/rest/LoginEndpoints.ts +++ b/src/rest/LoginEndpoints.ts @@ -1,10 +1,9 @@ import StreamrClient from '../StreamrClient' -import { Todo } from '../types' import { getEndpointUrl } from '../utils' import authFetch, { AuthFetchError } from './authFetch' -async function getSessionToken(url: Todo, props: Todo) { +async function getSessionToken(url: string, props: any) { return authFetch( url, undefined, @@ -26,7 +25,7 @@ export class LoginEndpoints { this.client = client } - async getChallenge(address: Todo) { + async getChallenge(address: string) { this.client.debug('getChallenge %o', { address, }) @@ -40,7 +39,7 @@ export class LoginEndpoints { ) } - async sendChallengeResponse(challenge: Todo, signature: Todo, address: Todo) { + async sendChallengeResponse(challenge: string, signature: string, address: string) { this.client.debug('sendChallengeResponse %o', { challenge, signature, @@ -55,7 +54,7 @@ export class LoginEndpoints { return getSessionToken(url, props) } - async loginWithChallengeResponse(signingFunction: Todo, address: Todo) { + async loginWithChallengeResponse(signingFunction: (challenge: string) => Promise, address: string) { this.client.debug('loginWithChallengeResponse %o', { address, }) @@ -64,7 +63,7 @@ export class LoginEndpoints { return this.sendChallengeResponse(challenge, signature, address) } - async loginWithApiKey(apiKey: Todo) { + async loginWithApiKey(apiKey: string) { this.client.debug('loginWithApiKey %o', { apiKey, }) @@ -75,7 +74,7 @@ export class LoginEndpoints { return getSessionToken(url, props) } - async loginWithUsernamePassword(username: Todo, password: Todo) { + async loginWithUsernamePassword(username: string, password: string) { this.client.debug('loginWithUsernamePassword %o', { username, }) diff --git a/src/rest/StreamEndpoints.ts b/src/rest/StreamEndpoints.ts index 3b58d1f5f..95a8071f8 100644 --- a/src/rest/StreamEndpoints.ts +++ b/src/rest/StreamEndpoints.ts @@ -6,7 +6,7 @@ import debugFactory from 'debug' import { getEndpointUrl } from '../utils' import { validateOptions } from '../stream/utils' -import Stream from '../stream' +import Stream, { StreamOperation, StreamProperties } from '../stream' import StreamPart from '../stream/StreamPart' import { isKeyExchangeStream } from '../stream/KeyExchange' @@ -16,6 +16,20 @@ import StreamrClient from '../StreamrClient' const debug = debugFactory('StreamrClient') +export interface StreamListQuery { + name?: string + uiChannel?: boolean + noConfig?: boolean + search?: string + sortBy?: string + order?: 'asc'|'desc' + max?: number + offset?: number + grantedAccess?: boolean + publicAccess?: boolean + operation?: StreamOperation +} + const agentSettings = { keepAlive: true, keepAliveMsecs: 5000, @@ -46,7 +60,7 @@ export class StreamEndpoints { this.client = client } - async getStream(streamId: Todo) { + async getStream(streamId: string) { this.client.debug('getStream %o', { streamId, }) @@ -70,7 +84,7 @@ export class StreamEndpoints { } } - async listStreams(query: Todo = {}) { + async listStreams(query: StreamListQuery = {}) { this.client.debug('listStreams %o', { query, }) @@ -85,12 +99,13 @@ export class StreamEndpoints { }) const json = await this.listStreams({ name, + // @ts-expect-error public: false, }) return json[0] ? new Stream(this.client, json[0]) : undefined } - async createStream(props: Todo) { + async createStream(props: StreamProperties) { this.client.debug('createStream %o', { props, }) @@ -106,7 +121,7 @@ export class StreamEndpoints { return json ? new Stream(this.client, json) : undefined } - async getOrCreateStream(props: Todo) { + async getOrCreateStream(props: { id?: string, name?: string }) { this.client.debug('getOrCreateStream %o', { props, }) @@ -133,7 +148,7 @@ export class StreamEndpoints { } } - async getStreamPublishers(streamId: Todo) { + async getStreamPublishers(streamId: string) { this.client.debug('getStreamPublishers %o', { streamId, }) @@ -142,7 +157,7 @@ export class StreamEndpoints { return json.addresses.map((a: string) => a.toLowerCase()) } - async isStreamPublisher(streamId: Todo, ethAddress: Todo) { + async isStreamPublisher(streamId: string, ethAddress: string) { this.client.debug('isStreamPublisher %o', { streamId, ethAddress, @@ -160,16 +175,16 @@ export class StreamEndpoints { } } - async getStreamSubscribers(streamId: Todo) { + async getStreamSubscribers(streamId: string) { this.client.debug('getStreamSubscribers %o', { streamId, }) const url = getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'subscribers') const json = await authFetch(url, this.client.session) - return json.addresses.map((a: Todo) => a.toLowerCase()) + return json.addresses.map((a: string) => a.toLowerCase()) } - async isStreamSubscriber(streamId: Todo, ethAddress: Todo) { + async isStreamSubscriber(streamId: string, ethAddress: string) { this.client.debug('isStreamSubscriber %o', { streamId, ethAddress, @@ -186,7 +201,7 @@ export class StreamEndpoints { } } - async getStreamValidationInfo(streamId: Todo) { + async getStreamValidationInfo(streamId: string) { this.client.debug('getStreamValidationInfo %o', { streamId, }) @@ -195,7 +210,7 @@ export class StreamEndpoints { return json } - async getStreamLast(streamObjectOrId: Todo) { + async getStreamLast(streamObjectOrId: Stream|string) { const { streamId, streamPartition = 0, count = 1 } = validateOptions(streamObjectOrId) this.client.debug('getStreamLast %o', { streamId, @@ -211,16 +226,16 @@ export class StreamEndpoints { return json } - async getStreamPartsByStorageNode(address: Todo) { + async getStreamPartsByStorageNode(address: string) { const json = await authFetch(getEndpointUrl(this.client.options.restUrl, 'storageNodes', address, 'streams'), this.client.session) - let result: Todo = [] - json.forEach((stream: Todo) => { + let result: StreamPart[] = [] + json.forEach((stream: { id: string, partitions: number }) => { result = result.concat(StreamPart.fromStream(stream)) }) return result } - async publishHttp(streamObjectOrId: Todo, data: Todo, requestOptions: Todo = {}, keepAlive: Todo = true) { + async publishHttp(streamObjectOrId: Stream|string, data: Todo, requestOptions: Todo = {}, keepAlive: boolean = true) { let streamId if (streamObjectOrId instanceof Stream) { streamId = streamObjectOrId.id @@ -239,7 +254,7 @@ export class StreamEndpoints { ...requestOptions, method: 'POST', body: JSON.stringify(data), - agent: keepAlive ? getKeepAliveAgentForUrl(this.client.options.restUrl) : undefined, + agent: keepAlive ? getKeepAliveAgentForUrl(this.client.options.restUrl!) : undefined, }, ) } diff --git a/src/stream/StreamPart.js b/src/stream/StreamPart.ts similarity index 62% rename from src/stream/StreamPart.js rename to src/stream/StreamPart.ts index e3a3eab4d..9371b7ae6 100644 --- a/src/stream/StreamPart.js +++ b/src/stream/StreamPart.ts @@ -1,11 +1,15 @@ export default class StreamPart { - constructor(streamId, streamPartition) { + + _streamId: string + _streamPartition: number + + constructor(streamId: string, streamPartition: number) { this._streamId = streamId this._streamPartition = streamPartition } - static fromStream({ id, partitions }) { - const result = [] + static fromStream({ id, partitions }: { id: string, partitions: number }) { + const result: StreamPart[] = [] for (let i = 0; i < partitions; i++) { result.push(new StreamPart(id, i)) } diff --git a/src/stream/index.ts b/src/stream/index.ts index 37117a808..7f9c4ac9f 100644 --- a/src/stream/index.ts +++ b/src/stream/index.ts @@ -5,6 +5,17 @@ import StorageNode from './StorageNode' import StreamrClient from '../StreamrClient' import { Todo } from '../types' +export enum StreamOperation { + STREAM_GET = 'stream_get', + STREAM_EDIT = 'stream_edit', + STREAM_DELETE = 'stream_delete', + STREAM_PUBLISH = 'stream_publish', + STREAM_SUBSCRIBE = 'stream_subscribe', + STREAM_SHARE = 'stream_share' +} + +export type StreamProperties = Todo + export default class Stream { // TODO add field definitions for all fields @@ -12,7 +23,7 @@ export default class Stream { id: string _client: StreamrClient - constructor(client: StreamrClient, props: Todo) { + constructor(client: StreamrClient, props: StreamProperties) { this._client = client Object.assign(this, props) } @@ -64,13 +75,13 @@ export default class Stream { ) } - async hasPermission(operation: Todo, userId: Todo) { + async hasPermission(operation: StreamOperation, userId: string|undefined) { // eth addresses may be in checksumcase, but userId from server has no case const userIdCaseInsensitive = typeof userId === 'string' ? userId.toLowerCase() : undefined // if not string then undefined const permissions = await this.getPermissions() - return permissions.find((p: Todo) => { + return permissions.find((p: any) => { if (p.operation !== operation) { return false } if (userIdCaseInsensitive === undefined) { @@ -80,8 +91,8 @@ export default class Stream { }) } - async grantPermission(operation: Todo, userId: Todo) { - const permissionObject: Todo = { + async grantPermission(operation: StreamOperation, userId: string|undefined) { + const permissionObject: any = { operation, } @@ -103,7 +114,7 @@ export default class Stream { ) } - async revokePermission(permissionId: Todo) { + async revokePermission(permissionId: number) { return authFetch( getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'permissions', permissionId), this._client.session, @@ -120,7 +131,7 @@ export default class Stream { ) } - async addToStorageNode(address: Todo) { + async addToStorageNode(address: string) { return authFetch( getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'storageNodes'), this._client.session, @@ -133,7 +144,7 @@ export default class Stream { ) } - async removeFromStorageNode(address: Todo) { + async removeFromStorageNode(address: string) { return authFetch( getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'storageNodes', address), this._client.session, @@ -148,7 +159,7 @@ export default class Stream { getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'storageNodes'), this._client.session, ) - return json.map((item: Todo) => new StorageNode(item.storageNodeAddress)) + return json.map((item: any) => new StorageNode(item.storageNodeAddress)) } async publish(...theArgs: Todo) { diff --git a/src/types.ts b/src/types.ts index ff7342d4a..a90812a10 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,3 +1 @@ export type Todo = any - -export type StreamrClientAndEndpoints = any \ No newline at end of file diff --git a/test/integration/LoginEndpoints.test.js b/test/integration/LoginEndpoints.test.js index 9ba86f428..a544a483b 100644 --- a/test/integration/LoginEndpoints.test.js +++ b/test/integration/LoginEndpoints.test.js @@ -27,7 +27,7 @@ describe('LoginEndpoints', () => { describe('Challenge generation', () => { it('should retrieve a challenge', async () => { - const challenge = await client.getChallenge('some-address') + const challenge = await client.loginEndpoints.getChallenge('some-address') assert(challenge) assert(challenge.id) assert(challenge.challenge) @@ -38,7 +38,7 @@ describe('LoginEndpoints', () => { describe('Challenge response', () => { it('should fail to get a session token', async () => { await expect(async () => { - await client.sendChallengeResponse({ + await client.loginEndpoints.sendChallengeResponse({ id: 'some-id', challenge: 'some-challenge', }, 'some-sig', 'some-address') @@ -47,10 +47,10 @@ describe('LoginEndpoints', () => { it('should get a session token', async () => { const wallet = ethers.Wallet.createRandom() - const challenge = await client.getChallenge(wallet.address) + const challenge = await client.loginEndpoints.getChallenge(wallet.address) assert(challenge.challenge) const signature = await wallet.signMessage(challenge.challenge) - const sessionToken = await client.sendChallengeResponse(challenge, signature, wallet.address) + const sessionToken = await client.loginEndpoints.sendChallengeResponse(challenge, signature, wallet.address) assert(sessionToken) assert(sessionToken.token) assert(sessionToken.expires) @@ -58,7 +58,7 @@ describe('LoginEndpoints', () => { it('should get a session token with combined function', async () => { const wallet = ethers.Wallet.createRandom() - const sessionToken = await client.loginWithChallengeResponse((d) => wallet.signMessage(d), wallet.address) + const sessionToken = await client.loginEndpoints.loginWithChallengeResponse((d) => wallet.signMessage(d), wallet.address) assert(sessionToken) assert(sessionToken.token) assert(sessionToken.expires) @@ -73,7 +73,7 @@ describe('LoginEndpoints', () => { }) it('should get a session token', async () => { - const sessionToken = await client.loginWithApiKey('tester1-api-key') + const sessionToken = await client.loginEndpoints.loginWithApiKey('tester1-api-key') assert(sessionToken) assert(sessionToken.token) assert(sessionToken.expires) @@ -83,14 +83,14 @@ describe('LoginEndpoints', () => { describe('Username/password login', () => { it('should fail', async () => { await expect(async () => { - await client.loginWithUsernamePassword('username', 'password') + await client.loginEndpoints.loginWithUsernamePassword('username', 'password') }).rejects.toThrow('no longer supported') }) }) describe('UserInfo', () => { it('should get user info', async () => { - const userInfo = await client.getUserInfo() + const userInfo = await client.loginEndpoints.getUserInfo() assert(userInfo.name) assert(userInfo.username) }) @@ -100,7 +100,7 @@ describe('LoginEndpoints', () => { it('should not be able to use the same session token after logout', async () => { await client.getUserInfo() // first fetches the session token, then requests the endpoint const sessionToken1 = client.session.options.sessionToken - await client.logoutEndpoint() // invalidates the session token in engine-and-editor + await client.loginEndpoints.logoutEndpoint() // invalidates the session token in engine-and-editor await client.getUserInfo() // requests the endpoint with sessionToken1, receives 401, fetches a new session token const sessionToken2 = client.session.options.sessionToken assert.notDeepStrictEqual(sessionToken1, sessionToken2) diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.js index e72aae728..7f50b741f 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.js @@ -230,7 +230,7 @@ function TestStreamEndpoints(getName) { }) }) - describe.only('Storage node assignment', () => { + describe('Storage node assignment', () => { it('add', async () => { const storageNodeAddress = ethers.Wallet.createRandom().address const stream = await client.createStream() diff --git a/test/unit/Session.test.js b/test/unit/Session.test.js index 52309eba4..08046b45d 100644 --- a/test/unit/Session.test.js +++ b/test/unit/Session.test.js @@ -23,7 +23,9 @@ describe('Session', () => { sessionToken: 'session-token', }, }) - clientSessionToken.logoutEndpoint = sinon.stub().resolves() + clientSessionToken.loginEndpoints = { + logoutEndpoint: sinon.stub().resolves() + } session = new Session(clientSessionToken) session.options.unauthenticated = false @@ -160,7 +162,7 @@ describe('Session', () => { it('should call the logout endpoint', async () => { await session.getSessionToken() await session.logout() - expect(clientSessionToken.logoutEndpoint.calledOnce).toBeTruthy() + expect(clientSessionToken.loginEndpoints.logoutEndpoint.calledOnce).toBeTruthy() }) it('should call the logout endpoint again', async () => { @@ -169,7 +171,7 @@ describe('Session', () => { await session.logout() await session.getSessionToken() await session.logout() - expect(clientSessionToken.logoutEndpoint.calledTwice).toBeTruthy() + expect(clientSessionToken.loginEndpoints.logoutEndpoint.calledTwice).toBeTruthy() }) it('should throw if already logging out', async () => { From 5753cb5c0064499f6c01cf3a7756b08dd4bce1de Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 16 Feb 2021 15:14:25 +0200 Subject: [PATCH 430/517] Hide private login endpoints --- src/StreamrClient.ts | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index a4cd0fb71..a5a045960 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -464,35 +464,11 @@ export default class StreamrClient extends EventEmitter { async publishHttp(streamObjectOrId: Stream|string, data: Todo, requestOptions: Todo = {}, keepAlive: boolean = true) { return this.streamEndpoints.publishHttp(streamObjectOrId, data, requestOptions, keepAlive) } - - async getChallenge(address: Todo) { - return this.loginEndpoints.getChallenge(address) - } - - async sendChallengeResponse(challenge: Todo, signature: Todo, address: Todo) { - return this.loginEndpoints.sendChallengeResponse(challenge, signature, address) - } - - async loginWithChallengeResponse(signingFunction: Todo, address: Todo) { - return this.loginEndpoints.loginWithChallengeResponse(signingFunction, address) - } - - async loginWithApiKey(apiKey: Todo) { - return this.loginEndpoints.loginWithApiKey(apiKey) - } - - async loginWithUsernamePassword(username: Todo, password: Todo) { - return this.loginEndpoints.loginWithUsernamePassword(username, password) - } async getUserInfo() { return this.loginEndpoints.getUserInfo() } - async logoutEndpoint() { - return this.loginEndpoints.logoutEndpoint() - } - async calculateDataUnionMainnetAddress(dataUnionName: string, deployerAddress: string, options: DataUnionOptions) { return this.dataUnionEndpoints.calculateDataUnionMainnetAddress(dataUnionName, deployerAddress, options) } From beefb7cb10b8fdc071211ef9bc09f93feda5a12b Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 16 Feb 2021 16:33:09 -0500 Subject: [PATCH 431/517] Retry browser tests. --- .github/workflows/nodejs.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index cda454975..5051e46c5 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -157,9 +157,14 @@ jobs: uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.2 with: services-to-start: "mysql redis engine-and-editor cassandra parity-node0 parity-sidechain-node0 bridge broker-node-storage-1 nginx smtp" - - name: test-browser - timeout-minutes: 2 - run: npm run test-browser + + - uses: nick-invision/retry@v2 + name: Run Test + with: + max_attempts: 3 + timeout_minutes: 3 + retry_on: error + command: npm run test-browser benchmarks: name: Test Benchmark using Node ${{ matrix.node-version }} From c2050597b6afdae26c112d1ffcbe3c74b3df006c Mon Sep 17 00:00:00 2001 From: Tuomas Koponen Date: Wed, 3 Feb 2021 15:09:00 +0200 Subject: [PATCH 432/517] Add detectFields implementation to Stream --- src/stream/index.ts | 24 +++++-- src/utils/FieldDetector.js | 33 ++++++++++ src/utils/index.js | 3 +- test/integration/Stream.test.js | 107 ++++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+), 6 deletions(-) create mode 100644 src/utils/FieldDetector.js create mode 100644 test/integration/Stream.test.js diff --git a/src/stream/index.ts b/src/stream/index.ts index 7f9c4ac9f..11a8d879e 100644 --- a/src/stream/index.ts +++ b/src/stream/index.ts @@ -1,4 +1,4 @@ -import { getEndpointUrl } from '../utils' +import { getEndpointUrl, FieldDetector } from '../utils' import authFetch from '../rest/authFetch' import StorageNode from './StorageNode' @@ -125,10 +125,24 @@ export default class Stream { } async detectFields() { - return authFetch( - getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'detectFields'), - this._client.session, - ) + // Get last message of the stream to be used for field detecting + const sub = await this._client.resend({ + stream: this.id, + resend: { + last: 1, + }, + }) + const receivedMsgs = await sub.collect() + + if (receivedMsgs.length > 0) { + const lastMessage = receivedMsgs[0] + const fd = new FieldDetector(lastMessage) + const fields = fd.detect() + + // Save field config back to the stream + this.config.fields = fields + await this.update() + } } async addToStorageNode(address: string) { diff --git a/src/utils/FieldDetector.js b/src/utils/FieldDetector.js new file mode 100644 index 000000000..69ba45d13 --- /dev/null +++ b/src/utils/FieldDetector.js @@ -0,0 +1,33 @@ +export default class FieldDetector { + message = null + + constructor(message) { + this.message = message + } + + detect() { + if (this.message == null) { + throw new Error('Invalid message provided to FieldDetector constructor') + } + + const content = this.message + const fields = [] + + Object.keys(content).forEach((key) => { + let type + if (Array.isArray(content[key])) { + type = 'list' + } else if ((typeof content[key]) === 'object') { + type = 'map' + } else { + type = typeof content[key] + } + fields.push({ + name: key, + type, + }) + }) + + return fields + } +} diff --git a/src/utils/index.js b/src/utils/index.js index f0cff05e9..51c258b2c 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -11,8 +11,9 @@ import pkg from '../../package.json' import AggregatedError from './AggregatedError' import Scaffold from './Scaffold' +import FieldDetector from './FieldDetector' -export { AggregatedError, Scaffold } +export { AggregatedError, Scaffold, FieldDetector } const UUID = uuidv4() diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js new file mode 100644 index 000000000..a8506e71b --- /dev/null +++ b/test/integration/Stream.test.js @@ -0,0 +1,107 @@ +import StreamrClient from '../../src' +import { uid, fakePrivateKey, getPublishTestMessages } from '../utils' + +import config from './config' + +const createClient = (opts = {}) => new StreamrClient({ + ...config.clientOptions, + auth: { + privateKey: fakePrivateKey(), + }, + autoConnect: false, + autoDisconnect: false, + ...opts, +}) + +describe('Stream', () => { + let client + let stream + + beforeEach(async () => { + client = createClient() + await client.connect() + + stream = await client.createStream({ + name: uid('stream-integration-test') + }) + }) + + afterEach(async () => { + await client.disconnect() + }) + + describe('detectFields()', () => { + it('does detect primitive types', async () => { + const msg = { + number: 123, + boolean: true, + object: { + k: 1, + v: 2, + }, + array: [1, 2, 3], + string: 'test', + } + const publishTestMessages = getPublishTestMessages(client, { + streamId: stream.id, + waitForLast: true, + createMessage: () => msg, + }) + await publishTestMessages(1) + + expect(stream.config.fields).toEqual([]) + await stream.detectFields() + expect(stream.config.fields).toEqual([ + { + name: 'number', + type: 'number', + }, + { + name: 'boolean', + type: 'boolean', + }, + { + name: 'object', + type: 'map', + }, + { + name: 'array', + type: 'list', + }, + { + name: 'string', + type: 'string', + }, + ]) + }) + + it('skips unsupported types', async () => { + const msg = { + null: null, + empty: {}, + func: () => null, + nonexistent: undefined, + symbol: Symbol('test'), + } + const publishTestMessages = getPublishTestMessages(client, { + streamId: stream.id, + waitForLast: true, + createMessage: () => msg, + }) + await publishTestMessages(1) + + expect(stream.config.fields).toEqual([]) + await stream.detectFields() + expect(stream.config.fields).toEqual([ + { + name: 'null', + type: 'map', + }, + { + name: 'empty', + type: 'map', + }, + ]) + }) + }) +}) From b0849615ea6a52f8974ee5c2c341812aeb2ac22b Mon Sep 17 00:00:00 2001 From: Tuomas Koponen Date: Fri, 12 Feb 2021 08:52:52 +0200 Subject: [PATCH 433/517] Remove FieldDetector class --- src/stream/index.ts | 20 +++++++++++++++++--- src/utils/FieldDetector.js | 33 --------------------------------- src/utils/index.js | 3 +-- 3 files changed, 18 insertions(+), 38 deletions(-) delete mode 100644 src/utils/FieldDetector.js diff --git a/src/stream/index.ts b/src/stream/index.ts index 11a8d879e..1b4130601 100644 --- a/src/stream/index.ts +++ b/src/stream/index.ts @@ -1,4 +1,4 @@ -import { getEndpointUrl, FieldDetector } from '../utils' +import { getEndpointUrl } from '../utils' import authFetch from '../rest/authFetch' import StorageNode from './StorageNode' @@ -136,8 +136,22 @@ export default class Stream { if (receivedMsgs.length > 0) { const lastMessage = receivedMsgs[0] - const fd = new FieldDetector(lastMessage) - const fields = fd.detect() + const fields = [] + + Object.keys(lastMessage).forEach((key) => { + let type + if (Array.isArray(lastMessage[key])) { + type = 'list' + } else if ((typeof lastMessage[key]) === 'object') { + type = 'map' + } else { + type = typeof lastMessage[key] + } + fields.push({ + name: key, + type, + }) + }) // Save field config back to the stream this.config.fields = fields diff --git a/src/utils/FieldDetector.js b/src/utils/FieldDetector.js deleted file mode 100644 index 69ba45d13..000000000 --- a/src/utils/FieldDetector.js +++ /dev/null @@ -1,33 +0,0 @@ -export default class FieldDetector { - message = null - - constructor(message) { - this.message = message - } - - detect() { - if (this.message == null) { - throw new Error('Invalid message provided to FieldDetector constructor') - } - - const content = this.message - const fields = [] - - Object.keys(content).forEach((key) => { - let type - if (Array.isArray(content[key])) { - type = 'list' - } else if ((typeof content[key]) === 'object') { - type = 'map' - } else { - type = typeof content[key] - } - fields.push({ - name: key, - type, - }) - }) - - return fields - } -} diff --git a/src/utils/index.js b/src/utils/index.js index 51c258b2c..f0cff05e9 100644 --- a/src/utils/index.js +++ b/src/utils/index.js @@ -11,9 +11,8 @@ import pkg from '../../package.json' import AggregatedError from './AggregatedError' import Scaffold from './Scaffold' -import FieldDetector from './FieldDetector' -export { AggregatedError, Scaffold, FieldDetector } +export { AggregatedError, Scaffold } const UUID = uuidv4() From b918b3d4069103374ddcbdb643dcb8a38e37dcc9 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 17 Feb 2021 14:44:48 -0500 Subject: [PATCH 434/517] Update resend/subscribe signature so onMessage is optional. --- src/StreamrClient.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index a5a045960..30d521ec0 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -337,7 +337,7 @@ export default class StreamrClient extends EventEmitter { return this.publisher.rotateGroupKey(...args) } - async subscribe(opts: Todo, onMessage: OnMessageCallback) { + async subscribe(opts: Todo, onMessage?: OnMessageCallback) { let subTask: Todo let sub: Todo const hasResend = !!(opts.resend || opts.from || opts.to || opts.last) @@ -372,7 +372,7 @@ export default class StreamrClient extends EventEmitter { await this.subscriber.unsubscribe(opts) } - async resend(opts: Todo, onMessage: OnMessageCallback) { + async resend(opts: Todo, onMessage?: OnMessageCallback) { const task = this.subscriber.resend(opts) if (typeof onMessage !== 'function') { return task From e67d00a4929aae90b743356b82c362529a049739 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 17 Feb 2021 14:45:41 -0500 Subject: [PATCH 435/517] Remove old Stream.detectFields test. --- test/integration/StreamEndpoints.test.js | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.js index 7f50b741f..c5ccb1a74 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.js @@ -176,30 +176,6 @@ function TestStreamEndpoints(getName) { }) }) - describe.skip('Stream configuration', () => { - it('Stream.detectFields', async () => { - await client.connect() - await client.publish(createdStream.id, { - foo: 'bar', - count: 0, - }) - // Need time to propagate to storage - await wait(10000) - const stream = await createdStream.detectFields() - expect(stream.config.fields).toEqual([ - { - name: 'foo', - type: 'string', - }, - { - name: 'count', - type: 'number', - }, - ]) - await client.disconnect() - }, 15000) - }) - describe('Stream permissions', () => { it('Stream.getPermissions', async () => { const permissions = await createdStream.getPermissions() From 061bbd89d202a81c8405f3bd961796c84d1618e9 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 17 Feb 2021 14:46:39 -0500 Subject: [PATCH 436/517] Fix client import in test, also verify config works after reloading stream info from server. --- test/integration/Stream.test.js | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index a8506e71b..56f112f1b 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -1,4 +1,4 @@ -import StreamrClient from '../../src' +import StreamrClient from '../../src/StreamrClient' import { uid, fakePrivateKey, getPublishTestMessages } from '../utils' import config from './config' @@ -51,7 +51,7 @@ describe('Stream', () => { expect(stream.config.fields).toEqual([]) await stream.detectFields() - expect(stream.config.fields).toEqual([ + const expectedFields = [ { name: 'number', type: 'number', @@ -72,7 +72,11 @@ describe('Stream', () => { name: 'string', type: 'string', }, - ]) + ] + + expect(stream.config.fields).toEqual(expectedFields) + const loadedStream = await client.getStream(stream.id) + expect(loadedStream.config.fields).toEqual(expectedFields) }) it('skips unsupported types', async () => { @@ -82,6 +86,7 @@ describe('Stream', () => { func: () => null, nonexistent: undefined, symbol: Symbol('test'), + // TODO: bigint: 10n, } const publishTestMessages = getPublishTestMessages(client, { streamId: stream.id, @@ -92,7 +97,7 @@ describe('Stream', () => { expect(stream.config.fields).toEqual([]) await stream.detectFields() - expect(stream.config.fields).toEqual([ + const expectedFields = [ { name: 'null', type: 'map', @@ -101,7 +106,12 @@ describe('Stream', () => { name: 'empty', type: 'map', }, - ]) + ] + + expect(stream.config.fields).toEqual(expectedFields) + + const loadedStream = await client.getStream(stream.id) + expect(loadedStream.config.fields).toEqual(expectedFields) }) }) }) From 2ce215789dba24799f614a4afad0c31a6a24f195 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 17 Feb 2021 14:47:41 -0500 Subject: [PATCH 437/517] Add types to stream fields, refactor detectFields to fix typing. --- src/stream/index.ts | 67 +++++++++++++++++++++++++++++---------------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/src/stream/index.ts b/src/stream/index.ts index 1b4130601..29245468a 100644 --- a/src/stream/index.ts +++ b/src/stream/index.ts @@ -16,11 +16,39 @@ export enum StreamOperation { export type StreamProperties = Todo -export default class Stream { +const VALID_FIELD_TYPES = ['number', 'string', 'boolean', 'list', 'map'] as const + +type Field = { + name: string; + type: typeof VALID_FIELD_TYPES[number]; +} +function getFieldType(value: any): (Field['type'] | undefined) { + const type = typeof value + switch (true) { + case Array.isArray(value): { + return 'list' + } + case type === 'object': { + return 'map' + } + case (VALID_FIELD_TYPES as ReadonlyArray).includes(type): { + // see https://github.com/microsoft/TypeScript/issues/36275 + return type as Field['type'] + } + default: { + return undefined + } + } +} + +export default class Stream { // TODO add field definitions for all fields // @ts-expect-error id: string + config: { + fields: Field[]; + } = { fields: [] } _client: StreamrClient constructor(client: StreamrClient, props: StreamProperties) { @@ -132,31 +160,24 @@ export default class Stream { last: 1, }, }) + const receivedMsgs = await sub.collect() - if (receivedMsgs.length > 0) { - const lastMessage = receivedMsgs[0] - const fields = [] - - Object.keys(lastMessage).forEach((key) => { - let type - if (Array.isArray(lastMessage[key])) { - type = 'list' - } else if ((typeof lastMessage[key]) === 'object') { - type = 'map' - } else { - type = typeof lastMessage[key] - } - fields.push({ - name: key, - type, - }) - }) + if (!receivedMsgs.length) { return } - // Save field config back to the stream - this.config.fields = fields - await this.update() - } + const [lastMessage] = receivedMsgs + + const fields = Object.entries(lastMessage).map(([name, value]) => { + const type = getFieldType(value) + return !!type && { + name, + type, + } + }).filter(Boolean) as Field[] // see https://github.com/microsoft/TypeScript/issues/30621 + + // Save field config back to the stream + this.config.fields = fields + await this.update() } async addToStorageNode(address: string) { From ba222508db1b2e576da9cc5435a74250565bb815 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 17 Feb 2021 16:21:43 -0500 Subject: [PATCH 438/517] Tidy unused. --- test/integration/StreamEndpoints.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.js index c5ccb1a74..6be2ee3c9 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.js @@ -1,5 +1,4 @@ import { ethers } from 'ethers' -import { wait } from 'streamr-test-utils' import StreamrClient from '../../src/StreamrClient' import { uid } from '../utils' From 537062dca4ce8ecaa266318002788b750bc3dfea Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 1 Feb 2021 11:51:53 -0500 Subject: [PATCH 439/517] Add end parameter to PushQueue.from & pipeline. Prevents premature closing of pipeline. --- src/utils/PushQueue.js | 10 ++++++---- src/utils/iterators.js | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/utils/PushQueue.js b/src/utils/PushQueue.js index c2e11fcb4..391ed930d 100644 --- a/src/utils/PushQueue.js +++ b/src/utils/PushQueue.js @@ -72,9 +72,9 @@ export default class PushQueue { this.iterator = this.iterate() } - static from(iterable, opts) { + static from(iterable, { end, ...opts } = {}) { const queue = new PushQueue([], opts) - queue.from(iterable) + queue.from(iterable, { end }) return queue } @@ -101,12 +101,14 @@ export default class PushQueue { return buffer } - async from(iterable) { + async from(iterable, { end = true } = {}) { try { for await (const item of iterable) { this.push(item) } - this.end() + if (end) { + this.end() + } } catch (err) { await this.throw(err) } diff --git a/src/utils/iterators.js b/src/utils/iterators.js index 9ec70dd70..cbe206dea 100644 --- a/src/utils/iterators.js +++ b/src/utils/iterators.js @@ -292,7 +292,7 @@ const isPipeline = Symbol('isPipeline') const getIsStream = (item) => typeof item.pipe === 'function' -export function pipeline(iterables = [], onFinally = () => {}, opts = {}) { +export function pipeline(iterables = [], onFinally = () => {}, { end, ...opts } = {}) { const cancelFns = new Set() let cancelled = false let error @@ -371,7 +371,7 @@ export function pipeline(iterables = [], onFinally = () => {}, opts = {}) { } if (prev && nextStream) { - prev = getIsStream(prev) ? prev : PushQueue.from(prev) + prev = getIsStream(prev) ? prev : PushQueue.from(prev, { end }) prev.id = prev.id || 'inter-' + nextStream.id prev.pipe(nextStream) From 1d95da2c7ec0f6bd5ca671d8ccb3a4e9cc8e9516 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 1 Feb 2021 13:04:08 -0500 Subject: [PATCH 440/517] Add tests for PushQueue.from end parameter. PushQueue.from should buffer sync iterable immediately. --- src/utils/PushQueue.js | 27 ++++-- test/unit/PushQueue.test.js | 170 +++++++++++++++++++++++++++++++++++- 2 files changed, 190 insertions(+), 7 deletions(-) diff --git a/src/utils/PushQueue.js b/src/utils/PushQueue.js index 391ed930d..4db8a6616 100644 --- a/src/utils/PushQueue.js +++ b/src/utils/PushQueue.js @@ -103,15 +103,26 @@ export default class PushQueue { async from(iterable, { end = true } = {}) { try { - for await (const item of iterable) { - this.push(item) - } - if (end) { - this.end() + // detect sync/async iterable and iterate appropriately + if (!iterable[Symbol.asyncIterator]) { + // sync iterables push into buffer immediately + for (const item of iterable) { + this.push(item) + } + } else { + for await (const item of iterable) { + this.push(item) + } } } catch (err) { - await this.throw(err) + return this.throw(err) + } + + if (end) { + this.end() } + + return Promise.resolve() } onEnd(...args) { @@ -128,6 +139,10 @@ export default class PushQueue { */ end(v) { + if (this.ended) { + return + } + if (v != null) { this.push(v) } diff --git a/test/unit/PushQueue.test.js b/test/unit/PushQueue.test.js index 045d008a2..1404a13c3 100644 --- a/test/unit/PushQueue.test.js +++ b/test/unit/PushQueue.test.js @@ -73,8 +73,9 @@ describe('PushQueue', () => { }) describe('from', () => { - it('supports iterable', async () => { + it('supports sync iterable', async () => { const q = PushQueue.from(expected) + expect(q.length).toBe(expected.length) const msgs = [] // will end when source ends @@ -94,6 +95,7 @@ describe('PushQueue', () => { it('supports async iterable', async () => { const q = PushQueue.from(generate()) + expect(q.length).toBe(0) // can't tell length upfront with async iterable const msgs = [] // will end when source ends @@ -111,7 +113,119 @@ describe('PushQueue', () => { expect(q.length).toBe(0) }) + it('errors if source errors', async () => { + expect.assertions(5) + const err = new Error('expected') + const q = PushQueue.from(async function* GenerateError() { + yield* generate() + throw err + }()) + expect(q.length).toBe(0) // can't tell length upfront with async iterable + + const msgs = [] + await expect(async () => { + for await (const msg of q) { + msgs.push(msg) + } + }).rejects.toThrow('expected') + + expect(msgs).toEqual(expected) + + // buffer should have drained at end + expect(q.length).toBe(0) + + // these should have no effect + q.push('c') + expect(q.length).toBe(0) + }) + + it('errors if source errors immediately', async () => { + expect.assertions(5) + const err = new Error('expected') + // eslint-disable-next-line require-yield + const q = PushQueue.from(async function* GenerateError() { + throw err + }()) + + expect(q.length).toBe(0) + + const msgs = [] + await expect(async () => { + for await (const msg of q) { + msgs.push(msg) + } + }).rejects.toThrow('expected') + + expect(msgs).toEqual([]) + + // buffer should have drained at end + expect(q.length).toBe(0) + + // these should have no effect + q.push('c') + expect(q.length).toBe(0) + }) + + it('errors if sync source errors immediately', async () => { + expect.assertions(5) + const err = new Error('expected') + // eslint-disable-next-line require-yield + const q = PushQueue.from(function* GenerateError() { + throw err + }()) + + expect(q.length).toBe(0) + + const msgs = [] + await expect(async () => { + for await (const msg of q) { + msgs.push(msg) + } + }).rejects.toThrow('expected') + + expect(msgs).toEqual([]) + + // buffer should have drained at end + expect(q.length).toBe(0) + + // these should have no effect + q.push('c') + expect(q.length).toBe(0) + }) + + it('can require manually ending', async () => { + expect.assertions(6) + const q = PushQueue.from(generate(), { + end: false, + }) + + const msgs = [] + const callEnd = jest.fn(() => { + let error + try { + expect(q.isWritable()).toEqual(true) + expect(q.isReadable()).toEqual(true) + } catch (err) { + error = err + } + q.end(error) + }) + // will NOT end when source ends + for await (const msg of q) { + msgs.push(msg) + if (msgs.length === expected.length) { + setTimeout(callEnd, 100) + } + } + + expect(callEnd).toHaveBeenCalledTimes(1) + expect(msgs).toEqual(expected) + expect(q.isWritable()).toEqual(false) + expect(q.isReadable()).toEqual(false) + }) + it('can be aborted while waiting', async () => { + expect.assertions(3) const startedWaiting = jest.fn() const ac = new AbortController() let q @@ -259,6 +373,23 @@ describe('PushQueue', () => { expect(q.length).toBe(0) }) + it('ignores end after end', async () => { + const q = new PushQueue() // wouldn't end on its own + q.push(...expected) + q.end() + const err = new Error('expected error') + q.end(err) + + const msgs = [] + for await (const msg of q) { + msgs.push(msg) + } + + expect(msgs).toEqual(expected) + + expect(q.length).toBe(0) + }) + it('works with pending next', async () => { const q = new PushQueue() q.push(expected[0]) // preload first item @@ -282,6 +413,25 @@ describe('PushQueue', () => { // buffer should have drained at end expect(q.length).toBe(0) }) + + it('works with end in loop', async () => { + const q = new PushQueue(expected) + const msgs = [] + for await (const msg of q) { + msgs.push(msg) // gets rest of messages + if (msgs.length === 2) { + q.end() + } + } + + expect(msgs).toEqual(expected) + + // buffer should have drained at end + expect(q.length).toBe(0) + + // buffer should have drained at end + expect(q.length).toBe(0) + }) }) describe('onEnd', () => { @@ -356,6 +506,24 @@ describe('PushQueue', () => { expect(msgs).toEqual(expected.slice(0, 1)) expect(onEnd).toHaveBeenCalledTimes(1) }) + + it('fires only after all items consumed and source is closed', async () => { + const onEnd = jest.fn() + const q = PushQueue.from(expected, { + onEnd, + }) + + expect(q.length).toBe(expected.length) + expect(onEnd).toHaveBeenCalledTimes(0) + const msgs = [] + for await (const msg of q) { + msgs.push(msg) + await wait(0) + expect(onEnd).toHaveBeenCalledTimes(0) + } + expect(msgs).toEqual(expected) + expect(onEnd).toHaveBeenCalledTimes(1) + }) }) it('reduces length as items are consumed', async () => { From e5857ffaeb23030fdb0d8babd55cf3062ad0ea76 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 1 Feb 2021 14:10:22 -0500 Subject: [PATCH 441/517] Add autoEnd option to PushQueue. --- src/utils/PushQueue.js | 17 ++-- src/utils/iterators.js | 17 +--- test/unit/PushQueue.test.js | 36 +++++++- test/unit/iterators.test.js | 171 ++++++++++++++++++++++++++++++++++++ 4 files changed, 219 insertions(+), 22 deletions(-) diff --git a/src/utils/PushQueue.js b/src/utils/PushQueue.js index 4db8a6616..e8350dd2d 100644 --- a/src/utils/PushQueue.js +++ b/src/utils/PushQueue.js @@ -42,7 +42,8 @@ export class AbortError extends Error { */ export default class PushQueue { - constructor(items = [], { signal, onEnd, timeout = 0 } = {}) { + constructor(items = [], { signal, onEnd, timeout = 0, autoEnd = true } = {}) { + this.autoEnd = autoEnd this.buffer = [...items] this.finished = false this.error = null // queued error @@ -72,9 +73,9 @@ export default class PushQueue { this.iterator = this.iterate() } - static from(iterable, { end, ...opts } = {}) { + static from(iterable, opts = {}) { const queue = new PushQueue([], opts) - queue.from(iterable, { end }) + queue.from(iterable) return queue } @@ -93,7 +94,9 @@ export default class PushQueue { tasks.push(task) } await Promise.all(tasks) - return buffer.end() + if (src.autoEnd) { + await buffer.end() + } })().catch((err) => { return buffer.throw(err) }) // no await @@ -101,7 +104,7 @@ export default class PushQueue { return buffer } - async from(iterable, { end = true } = {}) { + async from(iterable, { end = this.autoEnd } = {}) { try { // detect sync/async iterable and iterate appropriately if (!iterable[Symbol.asyncIterator]) { @@ -309,8 +312,8 @@ export default class PushQueue { }) } - pipe(next) { - return next.from(this) + pipe(next, opts) { + return next.from(this, opts) } async cancel(...args) { diff --git a/src/utils/iterators.js b/src/utils/iterators.js index cbe206dea..bfb70ffba 100644 --- a/src/utils/iterators.js +++ b/src/utils/iterators.js @@ -1,7 +1,5 @@ import pMemoize from 'p-memoize' -import PushQueue from './PushQueue' // eslint-disable-line import/no-cycle - import { Defer, pTimeout, allSettledValues, AggregatedError } from './index' /** @@ -290,7 +288,7 @@ export function CancelableGenerator(iterable, onFinally = () => {}, { timeout = const isPipeline = Symbol('isPipeline') -const getIsStream = (item) => typeof item.pipe === 'function' +const getIsStream = (item) => typeof item.from === 'function' export function pipeline(iterables = [], onFinally = () => {}, { end, ...opts } = {}) { const cancelFns = new Set() @@ -356,7 +354,6 @@ export function pipeline(iterables = [], onFinally = () => {}, { end, ...opts } prev = index === 0 ? firstSrc : _prev // take first "prev" from outer iterator, if one exists nextIterable = typeof next === 'function' ? next(prev) : next - let nextStream if (prev && nextIterable[isPipeline]) { nextIterable.setFirstSource(prev) @@ -366,15 +363,9 @@ export function pipeline(iterables = [], onFinally = () => {}, { end, ...opts } cancelFns.add(nextIterable) } - if (nextIterable && getIsStream(nextIterable)) { - nextStream = nextIterable - } - - if (prev && nextStream) { - prev = getIsStream(prev) ? prev : PushQueue.from(prev, { end }) - - prev.id = prev.id || 'inter-' + nextStream.id - prev.pipe(nextStream) + if (prev && nextIterable && getIsStream(nextIterable)) { + prev.id = prev.id || 'inter-' + nextIterable.id + nextIterable.from(prev, { end }) } yield* nextIterable diff --git a/test/unit/PushQueue.test.js b/test/unit/PushQueue.test.js index 1404a13c3..9245649de 100644 --- a/test/unit/PushQueue.test.js +++ b/test/unit/PushQueue.test.js @@ -193,9 +193,10 @@ describe('PushQueue', () => { expect(q.length).toBe(0) }) - it('can require manually ending', async () => { + it('can require manually ending with instance.from end: false', async () => { expect.assertions(6) - const q = PushQueue.from(generate(), { + const q = new PushQueue() + q.from(generate(), { end: false, }) @@ -224,6 +225,37 @@ describe('PushQueue', () => { expect(q.isReadable()).toEqual(false) }) + it('can require manually ending with autoEnd: false', async () => { + expect.assertions(6) + const q = PushQueue.from(generate(), { + autoEnd: false, + }) + + const msgs = [] + const callEnd = jest.fn(() => { + let error + try { + expect(q.isWritable()).toEqual(true) + expect(q.isReadable()).toEqual(true) + } catch (err) { + error = err + } + q.end(error) + }) + // will NOT end when source ends + for await (const msg of q) { + msgs.push(msg) + if (msgs.length === expected.length) { + setTimeout(callEnd, 100) + } + } + + expect(callEnd).toHaveBeenCalledTimes(1) + expect(msgs).toEqual(expected) + expect(q.isWritable()).toEqual(false) + expect(q.isReadable()).toEqual(false) + }) + it('can be aborted while waiting', async () => { expect.assertions(3) const startedWaiting = jest.fn() diff --git a/test/unit/iterators.test.js b/test/unit/iterators.test.js index 116e88f6d..268541443 100644 --- a/test/unit/iterators.test.js +++ b/test/unit/iterators.test.js @@ -1247,6 +1247,177 @@ describe('Iterator Utils', () => { expect(onInputStreamClose).toHaveBeenCalledTimes(1) }) + it('can have delayed yields', async () => { + const receivedStep1 = [] + const receivedStep2 = [] + const onThroughStreamClose = jest.fn() + const throughStream = PushQueue.from(generate(), { + onEnd: onThroughStreamClose, + }) + + const p = pipeline([ + throughStream, + async function* Step1(s) { + for await (const msg of s) { + receivedStep1.push(msg) + yield msg + } + await wait(10) + for (const msg of receivedStep1) { + yield msg * 2 + } + }, + async function* Step2(s) { + for await (const msg of s) { + receivedStep2.push(msg) + yield msg + } + }, + ], onFinally, { + timeout: WAIT, + }) + + const received = [] + for await (const msg of p) { + received.push(msg) + expect(onFinally).toHaveBeenCalledTimes(0) + } + + expect(received).toEqual([...expected, ...expected.map((v) => v * 2)]) + expect(receivedStep1).toEqual(expected) + + // all streams were closed + expect(onThroughStreamClose).toHaveBeenCalledTimes(1) + }) + + it('does not end internal non-autoEnd streams automatically', async () => { + const receivedStep1 = [] + const receivedStep2 = [] + const receivedStep3 = [] + const onThroughStreamClose = jest.fn() + const afterLoop1 = jest.fn() + const throughStream = new PushQueue([], { + autoEnd: false, + onEnd: onThroughStreamClose, + }) + + const p = pipeline([ + generate(), + // eslint-disable-next-line require-yield + async function* Step1(s) { + for await (const msg of s) { + receivedStep1.push(msg) + setTimeout(() => { + throughStream.push(msg) + }, 100) + } + afterLoop1() + }, + throughStream, + async function* Step2(s) { + for await (const msg of s) { + expect(afterLoop1).toHaveBeenCalledTimes(1) + receivedStep2.push(msg) + yield msg + if (receivedStep2.length === expected.length) { + // this should end pipeline + break + } + } + }, + async function* Step3(s) { + for await (const msg of s) { + receivedStep3.push(msg) + yield msg + // should close automatically + } + }, + ], onFinally, { + timeout: WAIT, + }) + + const received = [] + for await (const msg of p) { + received.push(msg) + expect(onFinally).toHaveBeenCalledTimes(0) + } + expect(received).toEqual(expected) + expect(receivedStep1).toEqual(expected) + expect(receivedStep2).toEqual(expected) + expect(receivedStep3).toEqual(expected) + + // all streams were closed + expect(onThroughStreamClose).toHaveBeenCalledTimes(1) + expect(afterLoop1).toHaveBeenCalledTimes(1) + }) + + it('does not end internal streams automatically if going through non-autoEnd: false PushQueue', async () => { + const receivedStep1 = [] + const receivedStep2 = [] + const receivedStep3 = [] + const onThroughStreamClose = jest.fn() + const onThroughStream2Close = jest.fn() + const afterLoop1 = jest.fn() + const throughStream = new PushQueue([], { + autoEnd: false, + onEnd: onThroughStreamClose, + }) + const throughStream2 = new PushQueue([], { + autoEnd: true, + onEnd: onThroughStream2Close, + }) + + const p = pipeline([ + generate(), + // eslint-disable-next-line require-yield + async function* Step1(s) { + for await (const msg of s) { + receivedStep1.push(msg) + setTimeout(() => { + throughStream.push(msg) + }, 100) + } + afterLoop1() + }, + throughStream, + async function* Step2(s) { + for await (const msg of s) { + expect(afterLoop1).toHaveBeenCalledTimes(1) + receivedStep2.push(msg) + yield msg + if (receivedStep2.length === expected.length) { + // end pipeline + break + } + } + }, + throughStream2, + async function* Step3(s) { + for await (const msg of s) { + receivedStep3.push(msg) + yield msg + // should close automatically + } + }, + ], onFinally, { + timeout: WAIT, + }) + + const received = [] + for await (const msg of p) { + received.push(msg) + expect(onFinally).toHaveBeenCalledTimes(0) + } + expect(received).toEqual(expected) + expect(receivedStep1).toEqual(expected) + expect(receivedStep2).toEqual(expected) + expect(receivedStep3).toEqual(expected) + + // all streams were closed + expect(onThroughStreamClose).toHaveBeenCalledTimes(1) + expect(onThroughStream2Close).toHaveBeenCalledTimes(1) + }) + it('works with nested pipelines', async () => { const onFinallyInnerAfter = jest.fn() const onFinallyInner = jest.fn(async () => { From 468eb5574085fbe7decd76615f105c417b98dbdb Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 1 Feb 2021 15:11:35 -0500 Subject: [PATCH 442/517] Use streamr-client-protocol@8.0.0-beta.1 --- package-lock.json | 45 +++++++++------------------------------------ package.json | 2 +- webpack.config.js | 2 +- 3 files changed, 11 insertions(+), 38 deletions(-) diff --git a/package-lock.json b/package-lock.json index 59d71601e..81956a426 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1207,15 +1207,6 @@ "regenerator-runtime": "^0.13.4" } }, - "@babel/runtime-corejs3": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.5.tgz", - "integrity": "sha512-roGr54CsTmNPPzZoCP1AmDXuBoNao7tnSA83TXTwt+UK5QVyh1DIJnrgYRPWKCF2flqZQXwa7Yr8v7VmLzF0YQ==", - "requires": { - "core-js-pure": "^3.0.0", - "regenerator-runtime": "^0.13.4" - } - }, "@babel/template": { "version": "7.12.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", @@ -3987,7 +3978,6 @@ "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, "requires": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" @@ -4662,7 +4652,8 @@ "core-js": { "version": "3.8.3", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.3.tgz", - "integrity": "sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q==" + "integrity": "sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q==", + "dev": true }, "core-js-compat": { "version": "3.8.2", @@ -4682,11 +4673,6 @@ } } }, - "core-js-pure": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.2.tgz", - "integrity": "sha512-v6zfIQqL/pzTVAbZvYUozsxNfxcFb6Ks3ZfEbuneJl3FW9Jb8F6vLWB6f+qTmAu72msUdyb84V8d/yBFf7FNnw==" - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -12581,22 +12567,11 @@ } }, "sha3": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/sha3/-/sha3-2.1.3.tgz", - "integrity": "sha512-Io53D4o9qOmf3Ow9p/DoGLQiQHhtuR0ulbyambvRSG+OX5yXExk2yYfvjHtb7AtOyk6K6+sPeK/qaowWc/E/GA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/sha3/-/sha3-2.1.4.tgz", + "integrity": "sha512-S8cNxbyb0UGUM2VhRD4Poe5N58gJnJsLJ5vC7FYWGUmGhcsj4++WaIOBFVDxlG0W3To6xBuiRh+i0Qp2oNCOtg==", "requires": { - "buffer": "5.6.0" - }, - "dependencies": { - "buffer": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz", - "integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==", - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4" - } - } + "buffer": "6.0.3" } }, "shallow-clone": { @@ -13176,12 +13151,10 @@ "dev": true }, "streamr-client-protocol": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/streamr-client-protocol/-/streamr-client-protocol-7.1.2.tgz", - "integrity": "sha512-JibsGEEGaqKHKhTSfxuy41bARCQQEAVIeCUvEHry9aJk03QrM/0/d2LdT7d02Naxv2HX1RF1fQnJ2FxAMA46Yg==", + "version": "8.0.0-beta.1", + "resolved": "https://registry.npmjs.org/streamr-client-protocol/-/streamr-client-protocol-8.0.0-beta.1.tgz", + "integrity": "sha512-sxkn5ef/sqCo6hC/wy2f81hy7ONR/8aYW+H6TlQ2tltwT/q983QBmGtOW2Y4mT0I2QX10f6NDUIlfluemydsyA==", "requires": { - "@babel/runtime-corejs3": "^7.12.5", - "core-js": "^3.8.1", "debug": "^4.3.1", "ethers": "^5.0.24", "eventemitter3": "^4.0.7", diff --git a/package.json b/package.json index c86672f1a..408b53492 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,7 @@ "qs": "^6.9.6", "quick-lru": "^5.1.1", "readable-stream": "^3.6.0", - "streamr-client-protocol": "^7.1.2", + "streamr-client-protocol": "^8.0.0-beta.1", "typescript": "^4.1.4", "uuid": "^8.3.2", "webpack-node-externals": "^2.5.2", diff --git a/webpack.config.js b/webpack.config.js index 52b22578e..076d340ce 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -125,7 +125,7 @@ module.exports = (env, argv) => { buffer: path.resolve(__dirname, 'node_modules', 'buffer'), 'node-fetch': path.resolve(__dirname, './src/shim/node-fetch.js'), 'node-webcrypto-ossl': path.resolve(__dirname, 'src/shim/crypto.js'), - 'streamr-client-protocol': path.resolve(__dirname, 'node_modules/streamr-client-protocol/src'), + 'streamr-client-protocol': path.resolve(__dirname, 'node_modules/streamr-client-protocol/dist/src'), } }, plugins: [ From 08f928026aa8d3c2829db2d2276f7ec0d213bd2c Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 1 Feb 2021 16:09:31 -0500 Subject: [PATCH 443/517] Fix decryption transform. --- src/utils/PushQueue.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/PushQueue.js b/src/utils/PushQueue.js index e8350dd2d..9911cae81 100644 --- a/src/utils/PushQueue.js +++ b/src/utils/PushQueue.js @@ -79,8 +79,8 @@ export default class PushQueue { return queue } - static transform(src, fn) { - const buffer = new PushQueue() + static transform(src, fn, opts = {}) { + const buffer = new PushQueue([], opts) const orderedFn = pOrderedResolve(fn) // push must be run in sequence ;(async () => { // eslint-disable-line semi-style const tasks = [] @@ -94,8 +94,8 @@ export default class PushQueue { tasks.push(task) } await Promise.all(tasks) - if (src.autoEnd) { - await buffer.end() + if (buffer.autoEnd) { + buffer.end() } })().catch((err) => { return buffer.throw(err) From 9f13e02b8ca130e09ea84c5416faa544b41c4d34 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 8 Feb 2021 11:28:44 -0500 Subject: [PATCH 444/517] Add resend gapfill test. WIP. --- src/subscribe/OrderMessages.js | 27 +++++++++++++++++++++++- src/subscribe/messageStream.js | 2 +- test/integration/GapFill.test.js | 35 ++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/subscribe/OrderMessages.js b/src/subscribe/OrderMessages.js index fb141e809..05b091e83 100644 --- a/src/subscribe/OrderMessages.js +++ b/src/subscribe/OrderMessages.js @@ -1,6 +1,7 @@ import { Utils } from 'streamr-client-protocol' import { pipeline } from '../utils/iterators' +import { Defer } from '../utils' import PushQueue from '../utils/PushQueue' import { validateOptions } from '../stream/utils' @@ -17,9 +18,20 @@ export default function OrderMessages(client, options = {}) { const { gapFillTimeout, retryResendAfter, gapFill = true } = client.options const { streamId, streamPartition } = validateOptions(options) - const outStream = new PushQueue() // output buffer + // output buffer + const outStream = new PushQueue([], { + // we can end when: + // input has closed (i.e. all messages sent) + // AND + // no gaps are pending + // AND + // gaps have been filled or failed + autoEnd: false, + }) let done = false + const inputDone = Defer() + const allHandled = Defer() const resendStreams = new Set() // holds outstanding resends for cleanup const orderingUtil = new OrderingUtil(streamId, streamPartition, (orderedMessage) => { @@ -71,6 +83,17 @@ export default function OrderMessages(client, options = {}) { } }, outStream, // consumer gets outStream + async function* IsDone(src) { + for await (const msg of src) { + if (!gapFill) { + orderingUtil.markMessageExplicitly(msg) + } + + orderingUtil.add(msg) + // note no yield + // orderingUtil writes to outStream itself + } + }, ], async (err) => { done = true orderingUtil.clearGaps() @@ -78,6 +101,8 @@ export default function OrderMessages(client, options = {}) { resendStreams.clear() await outStream.cancel(err) orderingUtil.clearGaps() + }, { + end: false, }), { markMessageExplicitly, }) diff --git a/src/subscribe/messageStream.js b/src/subscribe/messageStream.js index 36b90f355..85dbcb082 100644 --- a/src/subscribe/messageStream.js +++ b/src/subscribe/messageStream.js @@ -19,7 +19,7 @@ function getIsMatchingStreamMessage({ streamId, streamPartition = 0 }) { * Returns a PushQueue that will fill with messages. */ -export default function messageStream(connection, { streamId, streamPartition, isUnicast, type }, onFinally = () => {}) { +export default function messageStream(connection, { streamId, streamPartition, isUnicast, type }, onFinally = async () => {}) { if (!type) { // eslint-disable-next-line no-param-reassign type = isUnicast ? ControlMessage.TYPES.UnicastMessage : ControlMessage.TYPES.BroadcastMessage diff --git a/test/integration/GapFill.test.js b/test/integration/GapFill.test.js index af2b7e7ec..8a7c90980 100644 --- a/test/integration/GapFill.test.js +++ b/test/integration/GapFill.test.js @@ -202,6 +202,41 @@ describeRepeats('GapFill', () => { expect(client.connection.getState()).toBe('connected') }, 15000) + it('can fill gaps in resends', async () => { + const { parse } = client.connection + let count = 0 + client.connection.parse = (...args) => { + const msg = parse.call(client.connection, ...args) + if (!msg.streamMessage) { + return msg + } + + count += 1 + if (count === 3 || count === 4 || count === 7) { + return null + } + + return msg + } + + const published = await publishTestMessages(MAX_MESSAGES, { + timestamp: 111111, + waitForLast: true, + }) + + const sub = await client.resend({ + stream, + last: MAX_MESSAGES, + }) + const received = [] + for await (const m of sub) { + received.push(m.getParsedContent()) + // should not need to explicitly end + } + expect(received).toEqual(published) + expect(client.connection.getState()).toBe('connected') + }, 60000) + it('can fill gaps between resend and realtime', async () => { // publish 5 messages into storage const published = await publishTestMessages(5, { From d334d58f53a948cae48c077db5db15efb120f4de Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 10 Feb 2021 13:57:34 -0500 Subject: [PATCH 445/517] Add runtime-corejs3 to dependencies, update babel runtime deps. --- package-lock.json | 1386 ++++++++++++++++++++++----------------------- package.json | 5 +- 2 files changed, 691 insertions(+), 700 deletions(-) diff --git a/package-lock.json b/package-lock.json index 81956a426..4005cb40c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@babel/cli": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.12.10.tgz", - "integrity": "sha512-+y4ZnePpvWs1fc/LhZRTHkTesbXkyBYuOB+5CyodZqrEuETXi3zOVfpAQIdgC3lXbHLTDG9dQosxR9BhvLKDLQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.12.13.tgz", + "integrity": "sha512-Zto3HPeE0GRmaxobUl7NvFTo97NKe1zdAuWqTO8oka7nE0IIqZ4CFvuRZe1qf+ZMd7eHMhwqrecjwc10mjXo/g==", "dev": true, "requires": { "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents", @@ -23,34 +23,34 @@ } }, "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", "dev": true, "requires": { - "@babel/highlight": "^7.10.4" + "@babel/highlight": "^7.12.13" } }, "@babel/compat-data": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.7.tgz", - "integrity": "sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.13.tgz", + "integrity": "sha512-U/hshG5R+SIoW7HVWIdmy1cB7s3ki+r3FpyEZiCgpi4tFgPnX/vynY80ZGSASOIrUM6O7VxOgCZgdt7h97bUGg==", "dev": true }, "@babel/core": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.10.tgz", - "integrity": "sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.10", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.5", - "@babel/parser": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.10", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.13.tgz", + "integrity": "sha512-BQKE9kXkPlXHPeqissfxo0lySWJcYdEP0hdtJOH/iJfDdhOCcgtNCjftCJg3qqauB4h+lz2N6ixM++b9DN1Tcw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.12.13", + "@babel/helper-module-transforms": "^7.12.13", + "@babel/helpers": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.12.13", + "@babel/types": "^7.12.13", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", @@ -61,199 +61,188 @@ } }, "@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", + "version": "7.12.15", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.15.tgz", + "integrity": "sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ==", "dev": true, "requires": { - "@babel/types": "^7.12.11", + "@babel/types": "^7.12.13", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", - "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", + "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", "dev": true, "requires": { - "@babel/types": "^7.12.10" + "@babel/types": "^7.12.13" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", - "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz", + "integrity": "sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/helper-explode-assignable-expression": "^7.12.13", + "@babel/types": "^7.12.13" } }, "@babel/helper-compilation-targets": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz", - "integrity": "sha512-+qH6NrscMolUlzOYngSBMIOQpKUGPPsc61Bu5W10mg84LxZ7cmvnBHzARKbDoFxVvqqAbj6Tg6N7bSrWSPXMyw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.13.tgz", + "integrity": "sha512-dXof20y/6wB5HnLOGyLh/gobsMvDNoekcC+8MCV2iaTd5JemhFkPD73QB+tK3iFC9P0xJC73B6MvKkyUfS9cCw==", "dev": true, "requires": { - "@babel/compat-data": "^7.12.5", - "@babel/helper-validator-option": "^7.12.1", + "@babel/compat-data": "^7.12.13", + "@babel/helper-validator-option": "^7.12.11", "browserslist": "^4.14.5", "semver": "^5.5.0" } }, "@babel/helper-create-class-features-plugin": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", - "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.13.tgz", + "integrity": "sha512-Vs/e9wv7rakKYeywsmEBSRC9KtmE7Px+YBlESekLeJOF0zbGUicGfXSNi3o+tfXSNS48U/7K9mIOOCR79Cl3+Q==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.12.1", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.10.4" + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-member-expression-to-functions": "^7.12.13", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/helper-replace-supers": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz", - "integrity": "sha512-idnutvQPdpbduutvi3JVfEgcVIHooQnhvhx0Nk9isOINOIGYkZea1Pk2JlJRiUnMefrlvr0vkByATBY/mB4vjQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.13.tgz", + "integrity": "sha512-XC+kiA0J3at6E85dL5UnCYfVOcIZ834QcAY0TIpgUVnz0zDzg+0TtvZTnJ4g9L1dPRGe30Qi03XCIS4tYCLtqw==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-annotate-as-pure": "^7.12.13", "regexpu-core": "^4.7.1" } }, - "@babel/helper-define-map": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", - "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/types": "^7.10.5", - "lodash": "^4.17.19" - } - }, "@babel/helper-explode-assignable-expression": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", - "integrity": "sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.13.tgz", + "integrity": "sha512-5loeRNvMo9mx1dA/d6yNi+YiKziJZFylZnCo1nmFF4qPU4yJ14abhWESuSMQSlQxWdxdOFzxXjk/PpfudTtYyw==", "dev": true, "requires": { - "@babel/types": "^7.12.1" + "@babel/types": "^7.12.13" } }, "@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" } }, "@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", "dev": true, "requires": { - "@babel/types": "^7.12.10" + "@babel/types": "^7.12.13" } }, "@babel/helper-hoist-variables": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", - "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.12.13.tgz", + "integrity": "sha512-KSC5XSj5HreRhYQtZ3cnSnQwDzgnbdUDEFsxkN0m6Q3WrCRt72xrnZ8+h+pX7YxM7hr87zIO3a/v5p/H3TrnVw==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.12.13" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", - "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.13.tgz", + "integrity": "sha512-B+7nN0gIL8FZ8SvMcF+EPyB21KnCcZHQZFczCxbiNGV/O0rsrSBlWGLzmtBJ3GMjSVMIm4lpFhR+VdVBuIsUcQ==", "dev": true, "requires": { - "@babel/types": "^7.12.7" + "@babel/types": "^7.12.13" } }, "@babel/helper-module-imports": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", - "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz", + "integrity": "sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==", "dev": true, "requires": { - "@babel/types": "^7.12.5" + "@babel/types": "^7.12.13" } }, "@babel/helper-module-transforms": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", - "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-simple-access": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/helper-validator-identifier": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.13.tgz", + "integrity": "sha512-acKF7EjqOR67ASIlDTupwkKM1eUisNAjaSduo5Cz+793ikfnpe7p4Q7B7EWU2PCoSTPWsQkR7hRUWEIZPiVLGA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-replace-supers": "^7.12.13", + "@babel/helper-simple-access": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/helper-validator-identifier": "^7.12.11", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.12.13", + "@babel/types": "^7.12.13", "lodash": "^4.17.19" } }, "@babel/helper-optimise-call-expression": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", - "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", + "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", "dev": true, "requires": { - "@babel/types": "^7.12.10" + "@babel/types": "^7.12.13" } }, "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", + "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", "dev": true }, "@babel/helper-remap-async-to-generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz", - "integrity": "sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.13.tgz", + "integrity": "sha512-Qa6PU9vNcj1NZacZZI1Mvwt+gXDH6CTfgAkSjeRMLE8HxtDK76+YDId6NQR+z7Rgd5arhD2cIbS74r0SxD6PDA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-wrap-function": "^7.10.4", - "@babel/types": "^7.12.1" + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-wrap-function": "^7.12.13", + "@babel/types": "^7.12.13" } }, "@babel/helper-replace-supers": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", - "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.13.tgz", + "integrity": "sha512-pctAOIAMVStI2TMLhozPKbf5yTEXc0OJa0eENheb4w09SrgOWEs+P4nTOZYJQCqs8JlErGLDPDJTiGIp3ygbLg==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.7", - "@babel/helper-optimise-call-expression": "^7.12.10", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.11" + "@babel/helper-member-expression-to-functions": "^7.12.13", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/traverse": "^7.12.13", + "@babel/types": "^7.12.13" } }, "@babel/helper-simple-access": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", - "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.13.tgz", + "integrity": "sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA==", "dev": true, "requires": { - "@babel/types": "^7.12.1" + "@babel/types": "^7.12.13" } }, "@babel/helper-skip-transparent-expression-wrappers": { @@ -266,12 +255,12 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", "dev": true, "requires": { - "@babel/types": "^7.12.11" + "@babel/types": "^7.12.13" } }, "@babel/helper-validator-identifier": { @@ -287,64 +276,64 @@ "dev": true }, "@babel/helper-wrap-function": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz", - "integrity": "sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.13.tgz", + "integrity": "sha512-t0aZFEmBJ1LojdtJnhOaQEVejnzYhyjWHSsNSNo8vOYRbAJNh6r6GQF7pd36SqG7OKGbn+AewVQ/0IfYfIuGdw==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/helper-function-name": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.12.13", + "@babel/types": "^7.12.13" } }, "@babel/helpers": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", - "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.13.tgz", + "integrity": "sha512-oohVzLRZ3GQEk4Cjhfs9YkJA4TdIDTObdBEZGrd6F/T0GPSnuV6l22eMcxlvcvzVIPH3VTtxbseudM1zIE+rPQ==", "dev": true, "requires": { - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.5", - "@babel/types": "^7.12.5" + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.12.13", + "@babel/types": "^7.12.13" } }, "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", + "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.4", + "@babel/helper-validator-identifier": "^7.12.11", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "version": "7.12.15", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.15.tgz", + "integrity": "sha512-AQBOU2Z9kWwSZMd6lNjCX0GUgFonL1wAM1db8L8PMk9UDaGsRCArBkU4Sc+UCM3AE4hjbXx+h58Lb3QT4oRmrA==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.12.tgz", - "integrity": "sha512-nrz9y0a4xmUrRq51bYkWJIO5SBZyG2ys2qinHsN0zHDHVsUaModrkpyWWWXfGqYQmOL3x9sQIcTNN/pBGpo09A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.13.tgz", + "integrity": "sha512-1KH46Hx4WqP77f978+5Ye/VUbuwQld2hph70yaw2hXS2v7ER2f3nlpNMu909HO2rbvP0NKLlMVDPh9KXklVMhA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.12.1", + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-remap-async-to-generator": "^7.12.13", "@babel/plugin-syntax-async-generators": "^7.8.0" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", - "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.13.tgz", + "integrity": "sha512-8SCJ0Ddrpwv4T7Gwb33EmW1V9PY5lggTO+A8WjyIwxrSHDUyBw4MtF96ifn1n8H806YlxbVCoKXbbmzD6RD+cA==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-class-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-proposal-dynamic-import": { @@ -358,105 +347,105 @@ } }, "@babel/plugin-proposal-export-namespace-from": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz", - "integrity": "sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.13.tgz", + "integrity": "sha512-INAgtFo4OnLN3Y/j0VwAgw3HDXcDtX+C/erMvWzuV9v71r7urb6iyMXu7eM9IgLr1ElLlOkaHjJ0SbCmdOQ3Iw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz", - "integrity": "sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.13.tgz", + "integrity": "sha512-v9eEi4GiORDg8x+Dmi5r8ibOe0VXoKDeNPYcTTxdGN4eOWikrJfDJCJrr1l5gKGvsNyGJbrfMftC2dTL6oz7pg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-json-strings": "^7.8.0" } }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz", - "integrity": "sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.13.tgz", + "integrity": "sha512-fqmiD3Lz7jVdK6kabeSr1PZlWSUVqSitmHEe3Z00dtGTKieWnX9beafvavc32kjORa5Bai4QNHgFDwWJP+WtSQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz", - "integrity": "sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.13.tgz", + "integrity": "sha512-Qoxpy+OxhDBI5kRqliJFAl4uWXk3Bn24WeFstPH0iLymFehSAUR8MHpqU7njyXv/qbo7oN6yTy5bfCmXdKpo1Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz", - "integrity": "sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.13.tgz", + "integrity": "sha512-O1jFia9R8BUCl3ZGB7eitaAPu62TXJRHn7rh+ojNERCFyqRwJMTmhz+tJ+k0CwI6CLjX/ee4qW74FSqlq9I35w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", - "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.13.tgz", + "integrity": "sha512-WvA1okB/0OS/N3Ldb3sziSrXg6sRphsBgqiccfcQq7woEn5wQLNX82Oc4PlaFcdwcWHuQXAtb8ftbS8Fbsg/sg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.12.1" + "@babel/plugin-transform-parameters": "^7.12.13" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", - "integrity": "sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.13.tgz", + "integrity": "sha512-9+MIm6msl9sHWg58NvqpNpLtuFbmpFYk37x8kgnGzAHvX35E1FyAwSUt5hIkSoWJFSAH+iwU8bJ4fcD1zKXOzg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz", - "integrity": "sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.13.tgz", + "integrity": "sha512-0ZwjGfTcnZqyV3y9DSD1Yk3ebp+sIUpT2YDqP8hovzaNZnQq2Kd7PEqa6iOIUDBXBt7Jl3P7YAcEIL5Pz8u09Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", - "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.13.tgz", + "integrity": "sha512-sV0V57uUwpauixvR7s2o75LmwJI6JECwm5oPUY5beZB1nBl2i37hc7CJGqB5G+58fur5Y6ugvl3LRONk5x34rg==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-class-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", - "integrity": "sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz", + "integrity": "sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-syntax-async-generators": { @@ -478,12 +467,12 @@ } }, "@babel/plugin-syntax-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", - "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-syntax-dynamic-import": { @@ -577,12 +566,12 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", - "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", + "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-syntax-typescript": { @@ -603,308 +592,307 @@ } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", - "integrity": "sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.13.tgz", + "integrity": "sha512-tBtuN6qtCTd+iHzVZVOMNp+L04iIJBpqkdY42tWbmjIT5wvR2kx7gxMBsyhQtFzHwBbyGi9h8J8r9HgnOpQHxg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz", - "integrity": "sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.13.tgz", + "integrity": "sha512-psM9QHcHaDr+HZpRuJcE1PXESuGWSCcbiGFFhhwfzdbTxaGDVzuVtdNYliAwcRo3GFg0Bc8MmI+AvIGYIJG04A==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.12.1" + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-remap-async-to-generator": "^7.12.13" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz", - "integrity": "sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz", + "integrity": "sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.12.tgz", - "integrity": "sha512-VOEPQ/ExOVqbukuP7BYJtI5ZxxsmegTwzZ04j1aF0dkSypGo9XpDHuOrABsJu+ie+penpSJheDJ11x1BEZNiyQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz", + "integrity": "sha512-Pxwe0iqWJX4fOOM2kEZeUuAxHMWb9nK+9oh5d11bsLoB0xMg+mkDpt0eYuDZB7ETrY9bbcVlKUGTOGWy7BHsMQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-classes": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz", - "integrity": "sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.13.tgz", + "integrity": "sha512-cqZlMlhCC1rVnxE5ZGMtIb896ijL90xppMiuWXcwcOAuFczynpd3KYemb91XFFPi3wJSe/OcrX9lXoowatkkxA==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-define-map": "^7.10.4", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-replace-supers": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", - "integrity": "sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.13.tgz", + "integrity": "sha512-dDfuROUPGK1mTtLKyDPUavmj2b6kFu82SmgpztBFEO974KMjJT+Ytj3/oWsTUMBmgPcp9J5Pc1SlcAYRpJ2hRA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-destructuring": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz", - "integrity": "sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.13.tgz", + "integrity": "sha512-Dn83KykIFzjhA3FDPA1z4N+yfF3btDGhjnJwxIj0T43tP0flCujnU8fKgEkf0C1biIpSv9NZegPBQ1J6jYkwvQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz", - "integrity": "sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz", + "integrity": "sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz", - "integrity": "sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz", + "integrity": "sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz", - "integrity": "sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz", + "integrity": "sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-for-of": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz", - "integrity": "sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.13.tgz", + "integrity": "sha512-xCbdgSzXYmHGyVX3+BsQjcd4hv4vA/FDy7Kc8eOpzKmBBPEOTurt0w5fCRQaGl+GSBORKgJdstQ1rHl4jbNseQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-function-name": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz", - "integrity": "sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz", + "integrity": "sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", - "integrity": "sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz", + "integrity": "sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz", - "integrity": "sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz", + "integrity": "sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", - "integrity": "sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.13.tgz", + "integrity": "sha512-JHLOU0o81m5UqG0Ulz/fPC68/v+UTuGTWaZBUwpEk1fYQ1D9LfKV6MPn4ttJKqRo5Lm460fkzjLTL4EHvCprvA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-module-transforms": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz", - "integrity": "sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.13.tgz", + "integrity": "sha512-OGQoeVXVi1259HjuoDnsQMlMkT9UkZT9TpXAsqWplS/M0N1g3TJAn/ByOCeQu7mfjc5WpSsRU+jV1Hd89ts0kQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-simple-access": "^7.12.1", + "@babel/helper-module-transforms": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-simple-access": "^7.12.13", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz", - "integrity": "sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.13.tgz", + "integrity": "sha512-aHfVjhZ8QekaNF/5aNdStCGzwTbU7SI5hUybBKlMzqIMC7w7Ho8hx5a4R/DkTHfRfLwHGGxSpFt9BfxKCoXKoA==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.10.4", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-validator-identifier": "^7.10.4", + "@babel/helper-hoist-variables": "^7.12.13", + "@babel/helper-module-transforms": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-validator-identifier": "^7.12.11", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz", - "integrity": "sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.13.tgz", + "integrity": "sha512-BgZndyABRML4z6ibpi7Z98m4EVLFI9tVsZDADC14AElFaNHHBcJIovflJ6wtCqFxwy2YJ1tJhGRsr0yLPKoN+w==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-module-transforms": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz", - "integrity": "sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz", + "integrity": "sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1" + "@babel/helper-create-regexp-features-plugin": "^7.12.13" } }, "@babel/plugin-transform-new-target": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", - "integrity": "sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz", + "integrity": "sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-object-super": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz", - "integrity": "sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz", + "integrity": "sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1" + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-replace-supers": "^7.12.13" } }, "@babel/plugin-transform-parameters": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", - "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.13.tgz", + "integrity": "sha512-e7QqwZalNiBRHCpJg/P8s/VJeSRYgmtWySs1JwvfwPqhBbiWfOcHDKdeAi6oAyIimoKWBlwc8oTgbZHdhCoVZA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-property-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz", - "integrity": "sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz", + "integrity": "sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-regenerator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", - "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.13.tgz", + "integrity": "sha512-lxb2ZAvSLyJ2PEe47hoGWPmW22v7CtSl9jW8mingV4H2sEX/JOcrAj2nPuGWi56ERUm2bUpjKzONAuT6HCn2EA==", "dev": true, "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", - "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz", + "integrity": "sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-runtime": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.10.tgz", - "integrity": "sha512-xOrUfzPxw7+WDm9igMgQCbO3cJKymX7dFdsgRr1eu9n3KjjyU4pptIXbXPseQDquw+W+RuJEJMHKHNsPNNm3CA==", + "version": "7.12.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.15.tgz", + "integrity": "sha512-OwptMSRnRWJo+tJ9v9wgAf72ydXWfYSXWhnQjZing8nGZSDFqU1MBleKM3+DriKkcbv7RagA8gVeB0A1PNlNow==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.12.5", - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13", "semver": "^5.5.1" } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", - "integrity": "sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz", + "integrity": "sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-spread": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz", - "integrity": "sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.13.tgz", + "integrity": "sha512-dUCrqPIowjqk5pXsx1zPftSq4sT0aCeZVAxhdgs3AMgyaDmoUT0G+5h3Dzja27t76aUEIJWlFgPJqJ/d4dbTtg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz", - "integrity": "sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz", + "integrity": "sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-template-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz", - "integrity": "sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.13.tgz", + "integrity": "sha512-arIKlWYUgmNsF28EyfmiQHJLJFlAJNYkuQO10jL46ggjBpeb2re1P9K9YGxNJB45BqTbaslVysXDYm/g3sN/Qg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.10.tgz", - "integrity": "sha512-JQ6H8Rnsogh//ijxspCjc21YPd3VLVoYtAwv3zQmqAt8YGYUtdo5usNhdl4b9/Vir2kPFZl6n1h0PfUz4hJhaA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz", + "integrity": "sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-typescript": { @@ -1075,50 +1063,50 @@ } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", - "integrity": "sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz", + "integrity": "sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", - "integrity": "sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz", + "integrity": "sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/preset-env": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.11.tgz", - "integrity": "sha512-j8Tb+KKIXKYlDBQyIOy4BLxzv1NUOwlHfZ74rvW+Z0Gp4/cI2IMDPBWAgWceGcE7aep9oL/0K9mlzlMGxA8yNw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.13.tgz", + "integrity": "sha512-JUVlizG8SoFTz4LmVUL8++aVwzwxcvey3N0j1tRbMAXVEy95uQ/cnEkmEKHN00Bwq4voAV3imQGnQvpkLAxsrw==", "dev": true, "requires": { - "@babel/compat-data": "^7.12.7", - "@babel/helper-compilation-targets": "^7.12.5", - "@babel/helper-module-imports": "^7.12.5", - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/compat-data": "^7.12.13", + "@babel/helper-compilation-targets": "^7.12.13", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/helper-validator-option": "^7.12.11", - "@babel/plugin-proposal-async-generator-functions": "^7.12.1", - "@babel/plugin-proposal-class-properties": "^7.12.1", + "@babel/plugin-proposal-async-generator-functions": "^7.12.13", + "@babel/plugin-proposal-class-properties": "^7.12.13", "@babel/plugin-proposal-dynamic-import": "^7.12.1", - "@babel/plugin-proposal-export-namespace-from": "^7.12.1", - "@babel/plugin-proposal-json-strings": "^7.12.1", - "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", - "@babel/plugin-proposal-numeric-separator": "^7.12.7", - "@babel/plugin-proposal-object-rest-spread": "^7.12.1", - "@babel/plugin-proposal-optional-catch-binding": "^7.12.1", - "@babel/plugin-proposal-optional-chaining": "^7.12.7", - "@babel/plugin-proposal-private-methods": "^7.12.1", - "@babel/plugin-proposal-unicode-property-regex": "^7.12.1", + "@babel/plugin-proposal-export-namespace-from": "^7.12.13", + "@babel/plugin-proposal-json-strings": "^7.12.13", + "@babel/plugin-proposal-logical-assignment-operators": "^7.12.13", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.13", + "@babel/plugin-proposal-numeric-separator": "^7.12.13", + "@babel/plugin-proposal-object-rest-spread": "^7.12.13", + "@babel/plugin-proposal-optional-catch-binding": "^7.12.13", + "@babel/plugin-proposal-optional-chaining": "^7.12.13", + "@babel/plugin-proposal-private-methods": "^7.12.13", + "@babel/plugin-proposal-unicode-property-regex": "^7.12.13", "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.12.1", + "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-dynamic-import": "^7.8.0", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", "@babel/plugin-syntax-json-strings": "^7.8.0", @@ -1128,41 +1116,41 @@ "@babel/plugin-syntax-object-rest-spread": "^7.8.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.12.1", - "@babel/plugin-transform-arrow-functions": "^7.12.1", - "@babel/plugin-transform-async-to-generator": "^7.12.1", - "@babel/plugin-transform-block-scoped-functions": "^7.12.1", - "@babel/plugin-transform-block-scoping": "^7.12.11", - "@babel/plugin-transform-classes": "^7.12.1", - "@babel/plugin-transform-computed-properties": "^7.12.1", - "@babel/plugin-transform-destructuring": "^7.12.1", - "@babel/plugin-transform-dotall-regex": "^7.12.1", - "@babel/plugin-transform-duplicate-keys": "^7.12.1", - "@babel/plugin-transform-exponentiation-operator": "^7.12.1", - "@babel/plugin-transform-for-of": "^7.12.1", - "@babel/plugin-transform-function-name": "^7.12.1", - "@babel/plugin-transform-literals": "^7.12.1", - "@babel/plugin-transform-member-expression-literals": "^7.12.1", - "@babel/plugin-transform-modules-amd": "^7.12.1", - "@babel/plugin-transform-modules-commonjs": "^7.12.1", - "@babel/plugin-transform-modules-systemjs": "^7.12.1", - "@babel/plugin-transform-modules-umd": "^7.12.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.1", - "@babel/plugin-transform-new-target": "^7.12.1", - "@babel/plugin-transform-object-super": "^7.12.1", - "@babel/plugin-transform-parameters": "^7.12.1", - "@babel/plugin-transform-property-literals": "^7.12.1", - "@babel/plugin-transform-regenerator": "^7.12.1", - "@babel/plugin-transform-reserved-words": "^7.12.1", - "@babel/plugin-transform-shorthand-properties": "^7.12.1", - "@babel/plugin-transform-spread": "^7.12.1", - "@babel/plugin-transform-sticky-regex": "^7.12.7", - "@babel/plugin-transform-template-literals": "^7.12.1", - "@babel/plugin-transform-typeof-symbol": "^7.12.10", - "@babel/plugin-transform-unicode-escapes": "^7.12.1", - "@babel/plugin-transform-unicode-regex": "^7.12.1", + "@babel/plugin-syntax-top-level-await": "^7.12.13", + "@babel/plugin-transform-arrow-functions": "^7.12.13", + "@babel/plugin-transform-async-to-generator": "^7.12.13", + "@babel/plugin-transform-block-scoped-functions": "^7.12.13", + "@babel/plugin-transform-block-scoping": "^7.12.13", + "@babel/plugin-transform-classes": "^7.12.13", + "@babel/plugin-transform-computed-properties": "^7.12.13", + "@babel/plugin-transform-destructuring": "^7.12.13", + "@babel/plugin-transform-dotall-regex": "^7.12.13", + "@babel/plugin-transform-duplicate-keys": "^7.12.13", + "@babel/plugin-transform-exponentiation-operator": "^7.12.13", + "@babel/plugin-transform-for-of": "^7.12.13", + "@babel/plugin-transform-function-name": "^7.12.13", + "@babel/plugin-transform-literals": "^7.12.13", + "@babel/plugin-transform-member-expression-literals": "^7.12.13", + "@babel/plugin-transform-modules-amd": "^7.12.13", + "@babel/plugin-transform-modules-commonjs": "^7.12.13", + "@babel/plugin-transform-modules-systemjs": "^7.12.13", + "@babel/plugin-transform-modules-umd": "^7.12.13", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.13", + "@babel/plugin-transform-new-target": "^7.12.13", + "@babel/plugin-transform-object-super": "^7.12.13", + "@babel/plugin-transform-parameters": "^7.12.13", + "@babel/plugin-transform-property-literals": "^7.12.13", + "@babel/plugin-transform-regenerator": "^7.12.13", + "@babel/plugin-transform-reserved-words": "^7.12.13", + "@babel/plugin-transform-shorthand-properties": "^7.12.13", + "@babel/plugin-transform-spread": "^7.12.13", + "@babel/plugin-transform-sticky-regex": "^7.12.13", + "@babel/plugin-transform-template-literals": "^7.12.13", + "@babel/plugin-transform-typeof-symbol": "^7.12.13", + "@babel/plugin-transform-unicode-escapes": "^7.12.13", + "@babel/plugin-transform-unicode-regex": "^7.12.13", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.12.11", + "@babel/types": "^7.12.13", "core-js-compat": "^3.8.0", "semver": "^5.5.0" } @@ -1200,36 +1188,45 @@ } }, "@babel/runtime": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.5.tgz", - "integrity": "sha512-plcc+hbExy3McchJCEQG3knOsuh3HH+Prx1P6cLIkET/0dLuQDEnrT+s27Axgc9bqfsmNUNHfscgMUdBpC9xfg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.13.tgz", + "integrity": "sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==", "requires": { "regenerator-runtime": "^0.13.4" } }, + "@babel/runtime-corejs3": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.13.tgz", + "integrity": "sha512-8fSpqYRETHATtNitsCXq8QQbKJP31/KnDl2Wz2Vtui9nKzjss2ysuZtyVsWjBtvkeEFo346gkwjYPab1hvrXkQ==", + "requires": { + "core-js-pure": "^3.0.0", + "regenerator-runtime": "^0.13.4" + } + }, "@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" } }, "@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz", + "integrity": "sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.12.13", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" @@ -1304,9 +1301,9 @@ } }, "@ethersproject/abi": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.0.10.tgz", - "integrity": "sha512-cfC3lGgotfxX3SMri4+CisOPwignoj/QGHW9J29spC4R4Qqcnk/SYuVkPFBMdLbvBp3f/pGiVqPNwont0TSXhg==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.0.12.tgz", + "integrity": "sha512-Ujr/3bwyYYjXLDQfebeiiTuvOw9XtUKM8av6YkoBeMXyGQM9GkjrQlwJMNwGTmqjATH/ZNbRgCh98GjOLiIB1Q==", "requires": { "@ethersproject/address": "^5.0.9", "@ethersproject/bignumber": "^5.0.13", @@ -1320,9 +1317,9 @@ } }, "@ethersproject/abstract-provider": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.0.8.tgz", - "integrity": "sha512-fqJXkewcGdi8LogKMgRyzc/Ls2js07yor7+g9KfPs09uPOcQLg7cc34JN+lk34HH9gg2HU0DIA5797ZR8znkfw==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.0.9.tgz", + "integrity": "sha512-X9fMkqpeu9ayC3JyBkeeZhn35P4xQkpGX/l+FrxDtEW9tybf/UWXSMi8bGThpPtfJ6q6U2LDetXSpSwK4TfYQQ==", "requires": { "@ethersproject/bignumber": "^5.0.13", "@ethersproject/bytes": "^5.0.9", @@ -1334,9 +1331,9 @@ } }, "@ethersproject/abstract-signer": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.0.11.tgz", - "integrity": "sha512-RKOgPSEYafknA62SrD3OCK42AllHE4YBfKYXyQeM+sBP7Nq3X5FpzeoY4uzC43P4wIhmNoTHCKQuwnX7fBqb6Q==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.0.12.tgz", + "integrity": "sha512-qt4jAEzQGPZ31My1gFGPzzJHJveYhVycW7RHkuX0W8fvMdg7wr0uvP7mQEptMVrb+jYwsVktCf6gBGwWDpFiTA==", "requires": { "@ethersproject/abstract-provider": "^5.0.8", "@ethersproject/bignumber": "^5.0.13", @@ -1346,9 +1343,9 @@ } }, "@ethersproject/address": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.0.9.tgz", - "integrity": "sha512-gKkmbZDMyGbVjr8nA5P0md1GgESqSGH7ILIrDidPdNXBl4adqbuA3OAuZx/O2oGpL6PtJ9BDa0kHheZ1ToHU3w==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.0.10.tgz", + "integrity": "sha512-70vqESmW5Srua1kMDIN6uVfdneZMaMyRYH4qPvkAXGkbicrCOsA9m01vIloA4wYiiF+HLEfL1ENKdn5jb9xiAw==", "requires": { "@ethersproject/bignumber": "^5.0.13", "@ethersproject/bytes": "^5.0.9", @@ -1358,26 +1355,26 @@ } }, "@ethersproject/base64": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.0.7.tgz", - "integrity": "sha512-S5oh5DVfCo06xwJXT8fQC68mvJfgScTl2AXvbYMsHNfIBTDb084Wx4iA9MNlEReOv6HulkS+gyrUM/j3514rSw==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.0.8.tgz", + "integrity": "sha512-PNbpHOMgZpZ1skvQl119pV2YkCPXmZTxw+T92qX0z7zaMFPypXWTZBzim+hUceb//zx4DFjeGT4aSjZRTOYThg==", "requires": { "@ethersproject/bytes": "^5.0.9" } }, "@ethersproject/basex": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.0.7.tgz", - "integrity": "sha512-OsXnRsujGmYD9LYyJlX+cVe5KfwgLUbUJrJMWdzRWogrygXd5HvGd7ygX1AYjlu1z8W/+t2FoQnczDR/H2iBjA==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.0.8.tgz", + "integrity": "sha512-PCVKZIShBQUqAXjJSvaCidThPvL0jaaQZcewJc0sf8Xx05BizaOS8r3jdPdpNdY+/qZtRDqwHTSKjvR/xssyLQ==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/properties": "^5.0.7" } }, "@ethersproject/bignumber": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.13.tgz", - "integrity": "sha512-b89bX5li6aK492yuPP5mPgRVgIxxBP7ksaBtKX5QQBsrZTpNOjf/MR4CjcUrAw8g+RQuD6kap9lPjFgY4U1/5A==", + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.14.tgz", + "integrity": "sha512-Q4TjMq9Gg3Xzj0aeJWqJgI3tdEiPiET7Y5OtNtjTAODZ2kp4y9jMNg97zVcvPedFvGROdpGDyCI77JDFodUzOw==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8", @@ -1385,25 +1382,25 @@ } }, "@ethersproject/bytes": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.9.tgz", - "integrity": "sha512-k+17ZViDtAugC0s7HM6rdsTWEdIYII4RPCDkPEuxKc6i40Bs+m6tjRAtCECX06wKZnrEoR9pjOJRXHJ/VLoOcA==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.10.tgz", + "integrity": "sha512-vpu0v1LZ1j1s9kERQIMnVU69MyHEzUff7nqK9XuCU4vx+AM8n9lU2gj7jtJIvGSt9HzatK/6I6bWusI5nyuaTA==", "requires": { "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/constants": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.8.tgz", - "integrity": "sha512-sCc73pFBsl59eDfoQR5OCEZCRv5b0iywadunti6MQIr5lt3XpwxK1Iuzd8XSFO02N9jUifvuZRrt0cY0+NBgTg==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.9.tgz", + "integrity": "sha512-2uAKH89UcaJP/Sc+54u92BtJtZ4cPgcS1p0YbB1L3tlkavwNvth+kNCUplIB1Becqs7BOZr0B/3dMNjhJDy4Dg==", "requires": { "@ethersproject/bignumber": "^5.0.13" } }, "@ethersproject/contracts": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.0.9.tgz", - "integrity": "sha512-CCTxVeDh6sjdSEbjzONhtwPjECvaHE62oGkY8M7kP0CHmgLD2SEGel0HZib8e5oQKRKGly9AKcUFW4g3rQ0AQw==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.0.10.tgz", + "integrity": "sha512-h9kdvllwT6B1LyUXeNQIb7Y6u6ZprP5LUiQIjSqvOehhm1sFZcaVtydsSa0LIg3SBC5QF0M7zH5p7EtI2VD0rQ==", "requires": { "@ethersproject/abi": "^5.0.10", "@ethersproject/abstract-provider": "^5.0.8", @@ -1417,9 +1414,9 @@ } }, "@ethersproject/hash": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.0.10.tgz", - "integrity": "sha512-Tf0bvs6YFhw28LuHnhlDWyr0xfcDxSXdwM4TcskeBbmXVSKLv3bJQEEEBFUcRX0fJuslR3gCVySEaSh7vuMx5w==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.0.11.tgz", + "integrity": "sha512-H3KJ9fk33XWJ2djAW03IL7fg3DsDMYjO1XijiUb1hJ85vYfhvxu0OmsU7d3tg2Uv1H1kFSo8ghr3WFQ8c+NL3g==", "requires": { "@ethersproject/abstract-signer": "^5.0.10", "@ethersproject/address": "^5.0.9", @@ -1432,9 +1429,9 @@ } }, "@ethersproject/hdnode": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.0.8.tgz", - "integrity": "sha512-Mscpjd7BBjxYSWghaNMwV0xrBBkOoCq6YEPRm9MgE24CiBlzzbfEB5DGq6hiZqhQaxPkdCUtKKqZi3nt9hx43g==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.0.9.tgz", + "integrity": "sha512-S5UMmIC6XfFtqhUK4uTjD8GPNzSbE+sZ/0VMqFnA3zAJ+cEFZuEyhZDYnl2ItGJzjT4jsy+uEy1SIl3baYK1PQ==", "requires": { "@ethersproject/abstract-signer": "^5.0.10", "@ethersproject/basex": "^5.0.7", @@ -1451,9 +1448,9 @@ } }, "@ethersproject/json-wallets": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.0.10.tgz", - "integrity": "sha512-Ux36u+d7Dm0M5AQ+mWuHdvfGPMN8K1aaLQgwzrsD4ELTWlwRuHuQbmn7/GqeOpbfaV6POLwdYcBk2TXjlGp/IQ==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.0.11.tgz", + "integrity": "sha512-0GhWScWUlXXb4qJNp0wmkU95QS3YdN9UMOfMSEl76CRANWWrmyzxcBVSXSBu5iQ0/W8wO+xGlJJ3tpA6v3mbIw==", "requires": { "@ethersproject/abstract-signer": "^5.0.10", "@ethersproject/address": "^5.0.9", @@ -1471,48 +1468,48 @@ } }, "@ethersproject/keccak256": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.0.7.tgz", - "integrity": "sha512-zpUBmofWvx9PGfc7IICobgFQSgNmTOGTGLUxSYqZzY/T+b4y/2o5eqf/GGmD7qnTGzKQ42YlLNo+LeDP2qe55g==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.0.8.tgz", + "integrity": "sha512-zoGbwXcWWs9MX4NOAZ7N0hhgIRl4Q/IO/u9c/RHRY4WqDy3Ywm0OLamEV53QDwhjwn3YiiVwU1Ve5j7yJ0a/KQ==", "requires": { "@ethersproject/bytes": "^5.0.9", "js-sha3": "0.5.7" } }, "@ethersproject/logger": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", - "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.9.tgz", + "integrity": "sha512-kV3Uamv3XOH99Xf3kpIG3ZkS7mBNYcLDM00JSDtNgNB4BihuyxpQzIZPRIDmRi+95Z/R1Bb0X2kUNHa/kJoVrw==" }, "@ethersproject/networks": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.0.7.tgz", - "integrity": "sha512-dI14QATndIcUgcCBL1c5vUr/YsI5cCHLN81rF7PU+yS7Xgp2/Rzbr9+YqpC6NBXHFUASjh6GpKqsVMpufAL0BQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.0.8.tgz", + "integrity": "sha512-PYpptlO2Tu5f/JEBI5hdlMds5k1DY1QwVbh3LKPb3un9dQA2bC51vd2/gRWAgSBpF3kkmZOj4FhD7ATLX4H+DA==", "requires": { "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/pbkdf2": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.0.7.tgz", - "integrity": "sha512-0SNLNixPMqnosH6pyc4yPiUu/C9/Jbu+f6I8GJW9U2qNpMBddmRJviwseoha5Zw1V+Aw0Z/yvYyzIIE8yPXqLA==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.0.8.tgz", + "integrity": "sha512-UlmAMGbIPaS2xXsI38FbePVTfJMuU9jnwcqVn3p88HxPF4kD897ha+l3TNsBqJqf32UbQL5GImnf1oJkSKq4vQ==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/sha2": "^5.0.7" } }, "@ethersproject/properties": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.7.tgz", - "integrity": "sha512-812H1Rus2vjw0zbasfDI1GLNPDsoyX1pYqiCgaR1BuyKxUTbwcH1B+214l6VGe1v+F6iEVb7WjIwMjKhb4EUsg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.8.tgz", + "integrity": "sha512-zEnLMze2Eu2VDPj/05QwCwMKHh506gpT9PP9KPVd4dDB+5d6AcROUYVLoIIQgBYK7X/Gw0UJmG3oVtnxOQafAw==", "requires": { "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/providers": { - "version": "5.0.19", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.0.19.tgz", - "integrity": "sha512-G+flo1jK1y/rvQy6b71+Nu7qOlkOKz+XqpgqFMZslkCzGuzQRmk9Qp7Ln4soK8RSyP1e5TCujaRf1H+EZahoaw==", + "version": "5.0.22", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.0.22.tgz", + "integrity": "sha512-6C6agQsz/7FRFfZFok+m1HVUS6G+IU1grLwBx9+W11SF3UeP8vquXRMVDfOiKWyvy+g4bJT1+9C/QuC8lG08DQ==", "requires": { "@ethersproject/abstract-provider": "^5.0.8", "@ethersproject/abstract-signer": "^5.0.10", @@ -1543,27 +1540,27 @@ } }, "@ethersproject/random": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.0.7.tgz", - "integrity": "sha512-PxSRWwN3s+FH9AWMZU6AcWJsNQ9KzqKV6NgdeKPtxahdDjCuXxTAuzTZNXNRK+qj+Il351UnweAGd+VuZcOAlQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.0.8.tgz", + "integrity": "sha512-4rHtotmd9NjklW0eDvByicEkL+qareIyFSbG1ShC8tPJJSAC0g55oQWzw+3nfdRCgBHRuEE7S8EcPcTVPvZ9cA==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/rlp": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.0.7.tgz", - "integrity": "sha512-ulUTVEuV7PT4jJTPpfhRHK57tkLEDEY9XSYJtrSNHOqdwMvH0z7BM2AKIMq4LVDlnu4YZASdKrkFGEIO712V9w==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.0.8.tgz", + "integrity": "sha512-E4wdFs8xRNJfzNHmnkC8w5fPeT4Wd1U2cust3YeT16/46iSkLT8nn8ilidC6KhR7hfuSZE4UqSPzyk76p7cdZg==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/sha2": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.0.7.tgz", - "integrity": "sha512-MbUqz68hhp5RsaZdqi1eg1rrtiqt5wmhRYqdA7MX8swBkzW2KiLgK+Oh25UcWhUhdi1ImU9qrV6if5j0cC7Bxg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.0.8.tgz", + "integrity": "sha512-ILP1ZgyvDj4rrdE+AXrTv9V88m7x87uga2VZ/FeULKPumOEw/4bGnJz/oQ8zDnDvVYRCJ+48VaQBS2CFLbk1ww==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8", @@ -1582,20 +1579,20 @@ } }, "@ethersproject/signing-key": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.0.8.tgz", - "integrity": "sha512-YKxQM45eDa6WAD+s3QZPdm1uW1MutzVuyoepdRRVmMJ8qkk7iOiIhUkZwqKLNxKzEJijt/82ycuOREc9WBNAKg==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.0.10.tgz", + "integrity": "sha512-w5it3GbFOvN6e0mTd5gDNj+bwSe6L9jqqYjU+uaYS8/hAEp4qYLk5p8ZjbJJkNn7u1p0iwocp8X9oH/OdK8apA==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8", "@ethersproject/properties": "^5.0.7", - "elliptic": "6.5.3" + "elliptic": "6.5.4" } }, "@ethersproject/solidity": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.0.8.tgz", - "integrity": "sha512-OJkyBq9KaoGsi8E8mYn6LX+vKyCURvxSp0yuGBcOqEFM3vkn9PsCiXsHdOXdNBvlHG5evJXwAYC2UR0TzgJeKA==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.0.9.tgz", + "integrity": "sha512-LIxSAYEQgLRXE3mRPCq39ou61kqP8fDrGqEeNcaNJS3aLbmAOS8MZp56uK++WsdI9hj8sNsFh78hrAa6zR9Jag==", "requires": { "@ethersproject/bignumber": "^5.0.13", "@ethersproject/bytes": "^5.0.9", @@ -1605,9 +1602,9 @@ } }, "@ethersproject/strings": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.8.tgz", - "integrity": "sha512-5IsdXf8tMY8QuHl8vTLnk9ehXDDm6x9FB9S9Og5IA1GYhLe5ZewydXSjlJlsqU2t9HRbfv97OJZV/pX8DVA/Hw==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.9.tgz", + "integrity": "sha512-ogxBpcUpdO524CYs841MoJHgHxEPUy0bJFDS4Ezg8My+WYVMfVAOlZSLss0Rurbeeam8CpUVDzM4zUn09SU66Q==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/constants": "^5.0.8", @@ -1615,9 +1612,9 @@ } }, "@ethersproject/transactions": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.0.9.tgz", - "integrity": "sha512-0Fu1yhdFBkrbMjenEr+39tmDxuHmaw0pe9Jb18XuKoItj7Z3p7+UzdHLr2S/okvHDHYPbZE5gtANDdQ3ZL1nBA==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.0.10.tgz", + "integrity": "sha512-Tqpp+vKYQyQdJQQk4M73tDzO7ODf2D42/sJOcKlDAAbdSni13v6a+31hUdo02qYXhVYwIs+ZjHnO4zKv5BNk8w==", "requires": { "@ethersproject/address": "^5.0.9", "@ethersproject/bignumber": "^5.0.13", @@ -1631,9 +1628,9 @@ } }, "@ethersproject/units": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.0.9.tgz", - "integrity": "sha512-4jIkcMVrJ3lCgXMO4M/2ww0/T/IN08vJTZld7FIAwa6aoBDTAy71+sby3sShl1SG3HEeKYbI3fBWauCUgPRUpQ==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.0.10.tgz", + "integrity": "sha512-eaiHi9ham5lbC7qpqxpae7OY/nHJUnRUnFFuEwi2VB5Nwe3Np468OAV+e+HR+jAK4fHXQE6PFBTxWGtnZuO37g==", "requires": { "@ethersproject/bignumber": "^5.0.13", "@ethersproject/constants": "^5.0.8", @@ -1641,9 +1638,9 @@ } }, "@ethersproject/wallet": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.0.10.tgz", - "integrity": "sha512-5siYr38NhqZKH6DUr6u4PdhgOKur8Q6sw+JID2TitEUmW0tOl8f6rpxAe77tw6SJT60D2UcvgsyLtl32+Nl+ig==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.0.11.tgz", + "integrity": "sha512-2Fg/DOvUltR7aZTOyWWlQhru+SKvq2UE3uEhXSyCFgMqDQNuc2nHXh1SHJtN65jsEbjVIppOe1Q7EQMvhmeeRw==", "requires": { "@ethersproject/abstract-provider": "^5.0.8", "@ethersproject/abstract-signer": "^5.0.10", @@ -1663,9 +1660,9 @@ } }, "@ethersproject/web": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.0.12.tgz", - "integrity": "sha512-gVxS5iW0bgidZ76kr7LsTxj4uzN5XpCLzvZrLp8TP+4YgxHfCeetFyQkRPgBEAJdNrexdSBayvyJvzGvOq0O8g==", + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.0.13.tgz", + "integrity": "sha512-G3x/Ns7pQm21ALnWLbdBI5XkW/jrsbXXffI9hKNPHqf59mTxHYtlNiSwxdoTSwCef3Hn7uvGZpaSgTyxs7IufQ==", "requires": { "@ethersproject/base64": "^5.0.7", "@ethersproject/bytes": "^5.0.9", @@ -1675,9 +1672,9 @@ } }, "@ethersproject/wordlists": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.0.8.tgz", - "integrity": "sha512-px2mloc1wAcdTbzv0ZotTx+Uh/dfnDO22D9Rx8xr7+/PUwAhZQjoJ9t7Hn72nsaN83rWBXsLvFcIRZju4GIaEQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.0.9.tgz", + "integrity": "sha512-Sn6MTjZkfbriod6GG6+p43W09HOXT4gwcDVNj0YoPYlo4Zq2Fk6b1CU9KUX3c6aI17PrgYb4qwZm5BMuORyqyQ==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/hash": "^5.0.10", @@ -2352,24 +2349,13 @@ } }, "@npmcli/move-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.0.tgz", - "integrity": "sha512-Iv2iq0JuyYjKeFkSR4LPaCdDZwlGK9X2cP/01nJcp3yMJ1FjNd9vpiEYvLUgzBxKPg2SFmaOhizoQsPc0LWeOQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.1.tgz", + "integrity": "sha512-LtWTicuF2wp7PNTuyCwABx7nNG+DnzSE8gN0iWxkC6mpgm/iOPu0ZMTkXuCxmJxtWFsDxUaixM9COSNJEMUfuQ==", "dev": true, "requires": { "mkdirp": "^1.0.4", - "rimraf": "^2.7.1" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } + "rimraf": "^3.0.2" } }, "@peculiar/asn1-schema": { @@ -2524,9 +2510,9 @@ } }, "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", "dev": true }, "@types/json5": { @@ -2536,9 +2522,9 @@ "dev": true }, "@types/node": { - "version": "14.14.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.21.tgz", - "integrity": "sha512-cHYfKsnwllYhjOzuC5q1VpguABBeecUp24yFluHpn/BQaVxB1CuQ1FSRZCzrPxrkIfWISXV2LbeoBthLWg0+0A==", + "version": "14.14.25", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.25.tgz", + "integrity": "sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ==", "dev": true }, "@types/normalize-package-data": { @@ -2548,9 +2534,9 @@ "dev": true }, "@types/prettier": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.6.tgz", - "integrity": "sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.0.tgz", + "integrity": "sha512-O3SQC6+6AySHwrspYn2UvC6tjo6jCTMMmylxZUFhE1CulVu5l3AxU6ca9lrJDTQDVllF62LIxVSx5fuYL6LiZg==", "dev": true }, "@types/qs": { @@ -2566,9 +2552,9 @@ "dev": true }, "@types/yargs": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz", - "integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==", + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -2871,24 +2857,24 @@ } }, "@webpack-cli/configtest": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.0.tgz", - "integrity": "sha512-Un0SdBoN1h4ACnIO7EiCjWuyhNI0Jl96JC+63q6xi4HDUYRZn8Auluea9D+v9NWKc5J4sICVEltdBaVjLX39xw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.1.tgz", + "integrity": "sha512-B+4uBUYhpzDXmwuo3V9yBH6cISwxEI4J+NO5ggDaGEEHb0osY/R7MzeKc0bHURXQuZjMM4qD+bSJCKIuI3eNBQ==", "dev": true }, "@webpack-cli/info": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.1.tgz", - "integrity": "sha512-fLnDML5HZ5AEKzHul8xLAksoKN2cibu6MgonkUj8R9V7bbeVRkd1XbGEGWrAUNYHbX1jcqCsDEpBviE5StPMzQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.2.tgz", + "integrity": "sha512-5U9kUJHnwU+FhKH4PWGZuBC1hTEPYyxGSL5jjoBI96Gx8qcYJGOikpiIpFoTq8mmgX3im2zAo2wanv/alD74KQ==", "dev": true, "requires": { "envinfo": "^7.7.3" } }, "@webpack-cli/serve": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.2.tgz", - "integrity": "sha512-03GkWxcgFfm8+WIwcsqJb9agrSDNDDoxaNnexPnCCexP5SCE4IgFd9lNpSy+K2nFqVMpgTFw6SwbmVAVTndVew==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.3.0.tgz", + "integrity": "sha512-k2p2VrONcYVX1wRRrf0f3X2VGltLWcv+JzXRBDmvCxGlCeESx4OXw91TsWeKOkp784uNoVQo313vxJFHXPPwfw==", "dev": true }, "@xtuc/ieee754": { @@ -3953,16 +3939,16 @@ } }, "browserslist": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz", - "integrity": "sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA==", + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", + "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001173", + "caniuse-lite": "^1.0.30001181", "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.634", + "electron-to-chromium": "^1.3.649", "escalade": "^3.1.1", - "node-releases": "^1.1.69" + "node-releases": "^1.1.70" } }, "bser": { @@ -4045,17 +4031,6 @@ "ssri": "^8.0.0", "tar": "^6.0.2", "unique-filename": "^1.1.1" - }, - "dependencies": { - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - } } }, "cache-base": { @@ -4098,9 +4073,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001178", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001178.tgz", - "integrity": "sha512-VtdZLC0vsXykKni8Uztx45xynytOi71Ufx9T8kHptSw9AL4dpqailUJJHavttuzUe1KYuBYtChiWv+BAb7mPmQ==", + "version": "1.0.30001185", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001185.tgz", + "integrity": "sha512-Fpi4kVNtNvJ15H0F6vwmXtb3tukv3Zg3qhKkOGUq7KJ1J6b9kf4dnNgtEAFXhRsJo0gNj9W60+wBvn0JcTvdTg==", "dev": true }, "capture-exit": { @@ -4160,9 +4135,9 @@ "dev": true }, "chokidar": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.0.tgz", - "integrity": "sha512-JgQM9JS92ZbFR4P90EvmzNpSGhpPBGBSj10PILeDyYFwp4h2/D9OM03wsJ4zW1fEp4ka2DGrnUeD7FuvQ2aZ2Q==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "dev": true, "optional": true, "requires": { @@ -4656,12 +4631,12 @@ "dev": true }, "core-js-compat": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.2.tgz", - "integrity": "sha512-LO8uL9lOIyRRrQmZxHZFl1RV+ZbcsAkFWTktn5SmH40WgLtSNYN4m4W2v9ONT147PxBY/XrRhrWq8TlvObyUjQ==", + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.3.tgz", + "integrity": "sha512-1sCb0wBXnBIL16pfFG1Gkvei6UzvKyTNYpiC41yrdjEv0UoJoq9E/abTMzyYJ6JpTkAj15dLjbqifIzEBDVvog==", "dev": true, "requires": { - "browserslist": "^4.16.0", + "browserslist": "^4.16.1", "semver": "7.0.0" }, "dependencies": { @@ -4673,6 +4648,11 @@ } } }, + "core-js-pure": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.3.tgz", + "integrity": "sha512-V5qQZVAr9K0xu7jXg1M7qTEwuxUgqr7dUOezGaNa7i+Xn9oXAU/d1fzqD9ObuwpVQOaorO5s70ckyi1woP9lVA==" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -5191,23 +5171,23 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.641", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.641.tgz", - "integrity": "sha512-b0DLhsHSHESC1I+Nx6n4w4Lr61chMd3m/av1rZQhS2IXTzaS5BMM5N+ldWdMIlni9CITMRM09m8He4+YV/92TA==", + "version": "1.3.661", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.661.tgz", + "integrity": "sha512-INNzKoL9ceOpPCpF5J+Fp9AOHY1RegwKViohAyTzV3XbkuRUx04r4v8edsDbevsog8UuL0GvD/Qerr2HwVTlSA==", "dev": true }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" } }, "emittery": { @@ -5306,9 +5286,9 @@ } }, "envinfo": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.3.tgz", - "integrity": "sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.4.tgz", + "integrity": "sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ==", "dev": true }, "errno": { @@ -5501,6 +5481,15 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", @@ -5931,40 +5920,40 @@ "dev": true }, "ethers": { - "version": "5.0.26", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.0.26.tgz", - "integrity": "sha512-MqA8Fvutn3qEW0yBJOHeV6KZmRpF2rqlL2B5058AGkUFsuu6j5Ns/FRlMsbGeQwBz801IB23jQp7vjRfFsKSkg==", - "requires": { - "@ethersproject/abi": "5.0.10", - "@ethersproject/abstract-provider": "5.0.8", - "@ethersproject/abstract-signer": "5.0.11", - "@ethersproject/address": "5.0.9", - "@ethersproject/base64": "5.0.7", - "@ethersproject/basex": "5.0.7", - "@ethersproject/bignumber": "5.0.13", - "@ethersproject/bytes": "5.0.9", - "@ethersproject/constants": "5.0.8", - "@ethersproject/contracts": "5.0.9", - "@ethersproject/hash": "5.0.10", - "@ethersproject/hdnode": "5.0.8", - "@ethersproject/json-wallets": "5.0.10", - "@ethersproject/keccak256": "5.0.7", - "@ethersproject/logger": "5.0.8", - "@ethersproject/networks": "5.0.7", - "@ethersproject/pbkdf2": "5.0.7", - "@ethersproject/properties": "5.0.7", - "@ethersproject/providers": "5.0.19", - "@ethersproject/random": "5.0.7", - "@ethersproject/rlp": "5.0.7", - "@ethersproject/sha2": "5.0.7", - "@ethersproject/signing-key": "5.0.8", - "@ethersproject/solidity": "5.0.8", - "@ethersproject/strings": "5.0.8", - "@ethersproject/transactions": "5.0.9", - "@ethersproject/units": "5.0.9", - "@ethersproject/wallet": "5.0.10", - "@ethersproject/web": "5.0.12", - "@ethersproject/wordlists": "5.0.8" + "version": "5.0.30", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.0.30.tgz", + "integrity": "sha512-CdY/zb8d0uEBaWNmDkAVWXO8FLs2plAPOjgukgYC95L5VKIZzZaCav7PAeG2IqGico4vtNu8l3ibXdXd6FqjrQ==", + "requires": { + "@ethersproject/abi": "5.0.12", + "@ethersproject/abstract-provider": "5.0.9", + "@ethersproject/abstract-signer": "5.0.12", + "@ethersproject/address": "5.0.10", + "@ethersproject/base64": "5.0.8", + "@ethersproject/basex": "5.0.8", + "@ethersproject/bignumber": "5.0.14", + "@ethersproject/bytes": "5.0.10", + "@ethersproject/constants": "5.0.9", + "@ethersproject/contracts": "5.0.10", + "@ethersproject/hash": "5.0.11", + "@ethersproject/hdnode": "5.0.9", + "@ethersproject/json-wallets": "5.0.11", + "@ethersproject/keccak256": "5.0.8", + "@ethersproject/logger": "5.0.9", + "@ethersproject/networks": "5.0.8", + "@ethersproject/pbkdf2": "5.0.8", + "@ethersproject/properties": "5.0.8", + "@ethersproject/providers": "5.0.22", + "@ethersproject/random": "5.0.8", + "@ethersproject/rlp": "5.0.8", + "@ethersproject/sha2": "5.0.8", + "@ethersproject/signing-key": "5.0.10", + "@ethersproject/solidity": "5.0.9", + "@ethersproject/strings": "5.0.9", + "@ethersproject/transactions": "5.0.10", + "@ethersproject/units": "5.0.10", + "@ethersproject/wallet": "5.0.11", + "@ethersproject/web": "5.0.13", + "@ethersproject/wordlists": "5.0.9" } }, "eventemitter3": { @@ -6813,9 +6802,9 @@ "dev": true }, "fsevents": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", - "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "optional": true }, @@ -6893,9 +6882,9 @@ "dev": true }, "get-intrinsic": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz", - "integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", "dev": true, "requires": { "function-bind": "^1.1.1", @@ -7122,9 +7111,9 @@ } }, "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", "dev": true }, "growl": { @@ -7534,9 +7523,9 @@ "dev": true }, "is-callable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", "dev": true }, "is-ci": { @@ -7714,11 +7703,12 @@ "dev": true }, "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", "dev": true, "requires": { + "call-bind": "^1.0.2", "has-symbols": "^1.0.1" } }, @@ -9550,9 +9540,9 @@ "dev": true }, "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", "dev": true, "requires": { "minimist": "^1.2.5" @@ -10741,9 +10731,9 @@ } }, "node-releases": { - "version": "1.1.69", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.69.tgz", - "integrity": "sha512-DGIjo79VDEyAnRlfSqYTsy+yoHd2IOjJiKUozD2MV2D85Vso6Bug56mb9tT/fY5Urt0iqk01H7x+llAruDR2zA==", + "version": "1.1.70", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.70.tgz", + "integrity": "sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==", "dev": true }, "node-status-codes": { @@ -11760,11 +11750,11 @@ "dev": true }, "pvtsutils": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.1.1.tgz", - "integrity": "sha512-Evbhe6L4Sxwu4SPLQ4LQZhgfWDQO3qa1lju9jM5cxsQp8vE10VipcSmo7hiJW48TmiHgVLgDtC2TL6/+ND+IVg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.1.2.tgz", + "integrity": "sha512-Yfm9Dsk1zfEpOWCaJaHfqtNXAFWNNHMFSCLN6jTnhuCCBCC2nqge4sAgo7UrkRBoAAYIL8TN/6LlLoNfZD/b5A==", "requires": { - "tslib": "^2.0.3" + "tslib": "^2.1.0" } }, "pvutils": { @@ -12106,9 +12096,9 @@ "dev": true }, "regjsparser": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.6.tgz", - "integrity": "sha512-jjyuCp+IEMIm3N1H1LLTJW1EISEJV9+5oHdEyrt43Pg9cDSb6rrLZei2cVWpl0xTjmmlpec/lEQGYgM7xfpGCQ==", + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.7.tgz", + "integrity": "sha512-ib77G0uxsA2ovgiYbCVGx4Pv3PSttAx2vIwidqQzbL2U5S4Q+j00HdSAneSBuyVcMvEnTXMjiGgB+DlXozVhpQ==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -12649,9 +12639,9 @@ } }, "sirv": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.10.tgz", - "integrity": "sha512-H5EZCoZaggEUQy8ocKsF7WAToGuZhjJlLvM3XOef46CbdIgbNeQ1p32N1PCuCjkVYwrAVOSMacN6CXXgIzuspg==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.11.tgz", + "integrity": "sha512-SR36i3/LSWja7AJNRBz4fF/Xjpn7lQFI30tZ434dIy+bitLYSP+ZEenHg36i23V2SGEz+kqjksg0uOGZ5LPiqg==", "dev": true, "requires": { "@polka/url": "^1.0.0-next.9", @@ -12919,9 +12909,9 @@ } }, "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "dev": true }, "spdx-correct": { @@ -12989,9 +12979,9 @@ } }, "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", "dev": true, "requires": { "minipass": "^3.1.1" @@ -13341,9 +13331,9 @@ }, "dependencies": { "ajv": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.0.tgz", - "integrity": "sha512-svS9uILze/cXbH0z2myCK2Brqprx/+JJYK5pHicT/GQiBfzzhUVAIT6MwqJg8y4xV/zoGsUeuPuwtoiKSGE15g==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.1.tgz", + "integrity": "sha512-ga/aqDYnUy/o7vbsRTFhhTsNeXiYb5JWDIcRIeZfwRNCefwjNTVYCGdGSUrEmiu3yDK3vFvNbgJxvrQW4JXrYQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -14139,15 +14129,15 @@ } }, "webcrypto-core": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.1.8.tgz", - "integrity": "sha512-hKnFXsqh0VloojNeTfrwFoRM4MnaWzH6vtXcaFcGjPEu+8HmBdQZnps3/2ikOFqS8bJN1RYr6mI2P/FJzyZnXg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.2.0.tgz", + "integrity": "sha512-p76Z/YLuE4CHCRdc49FB/ETaM4bzM3roqWNJeGs+QNY1fOTzKTOVnhmudW1fuO+5EZg6/4LG9NJ6gaAyxTk9XQ==", "requires": { - "@peculiar/asn1-schema": "^2.0.12", + "@peculiar/asn1-schema": "^2.0.27", "@peculiar/json-schema": "^1.1.12", "asn1js": "^2.0.26", - "pvtsutils": "^1.0.11", - "tslib": "^2.0.1" + "pvtsutils": "^1.1.2", + "tslib": "^2.1.0" } }, "webidl-conversions": { @@ -14492,17 +14482,17 @@ } }, "webpack-cli": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.4.0.tgz", - "integrity": "sha512-/Qh07CXfXEkMu5S8wEpjuaw2Zj/CC0hf/qbTDp6N8N7JjdGuaOjZ7kttz+zhuJO/J5m7alQEhNk9lsc4rC6xgQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.5.0.tgz", + "integrity": "sha512-wXg/ef6Ibstl2f50mnkcHblRPN/P9J4Nlod5Hg9HGFgSeF8rsqDGHJeVe4aR26q9l62TUJi6vmvC2Qz96YJw1Q==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.0.0", - "@webpack-cli/info": "^1.2.1", - "@webpack-cli/serve": "^1.2.2", + "@webpack-cli/configtest": "^1.0.1", + "@webpack-cli/info": "^1.2.2", + "@webpack-cli/serve": "^1.3.0", "colorette": "^1.2.1", - "commander": "^6.2.0", + "commander": "^7.0.0", "enquirer": "^2.3.6", "execa": "^5.0.0", "fastest-levenshtein": "^1.0.12", @@ -14514,9 +14504,9 @@ }, "dependencies": { "commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.0.0.tgz", + "integrity": "sha512-ovx/7NkTrnPuIV8sqk/GjUIIM1+iUQeqA3ye2VNpq9sVoiZsooObWlQy+OPWGI17GDaEoybuAGJm6U8yC077BA==", "dev": true }, "execa": { @@ -14772,9 +14762,9 @@ } }, "ws": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", - "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz", + "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==" }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index 408b53492..1ec726854 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/plugin-transform-classes": "^7.12.1", "@babel/plugin-transform-modules-commonjs": "^7.12.1", - "@babel/plugin-transform-runtime": "^7.12.10", + "@babel/plugin-transform-runtime": "^7.12.15", "@babel/preset-env": "^7.12.11", "@babel/preset-typescript": "^7.12.13", "@types/debug": "^4.1.5", @@ -87,7 +87,8 @@ }, "#IMPORTANT": "babel-runtime must be in dependencies, not devDependencies", "dependencies": { - "@babel/runtime": "^7.12.5", + "@babel/runtime": "^7.12.13", + "@babel/runtime-corejs3": "^7.12.13", "@ethersproject/address": "^5.0.9", "@ethersproject/bignumber": "^5.0.13", "@ethersproject/bytes": "^5.0.9", From 942f7c496f4a400a173ccd445246bd36a41c1c3d Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 11 Feb 2021 13:35:00 -0500 Subject: [PATCH 446/517] Fix jest module resolution with npm linked deps. --- jest.config.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/jest.config.js b/jest.config.js index 1086a1106..f273f3d05 100644 --- a/jest.config.js +++ b/jest.config.js @@ -60,9 +60,10 @@ module.exports = { // globals: {}, // An array of directory names to be searched recursively up from the requiring module's location - // moduleDirectories: [ - // "node_modules" - // ], + moduleDirectories: [ + 'node_modules', + path.resolve('./node_modules'), // makes npm link work. + ], // An array of file extensions your modules use // moduleFileExtensions: [ From 5be869d1bbe8c1290f8b6a5b136ff94e702f06df Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 11 Feb 2021 13:36:13 -0500 Subject: [PATCH 447/517] Default onFinally is async for type hinting. --- src/subscribe/index.js | 2 +- src/subscribe/pipeline.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/subscribe/index.js b/src/subscribe/index.js index 456ec49ef..db4af5fe1 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -319,7 +319,7 @@ class Subscriptions { this.subSessions = new Map() } - async add(opts, onFinally = () => {}) { + async add(opts, onFinally = async () => {}) { const options = validateOptions(opts) const { key } = options diff --git a/src/subscribe/pipeline.js b/src/subscribe/pipeline.js index 577c50d5e..9c1182c42 100644 --- a/src/subscribe/pipeline.js +++ b/src/subscribe/pipeline.js @@ -22,7 +22,7 @@ async function collect(src) { * Subscription message processing pipeline */ -export default function MessagePipeline(client, opts = {}, onFinally = () => {}) { +export default function MessagePipeline(client, opts = {}, onFinally = async () => {}) { const options = validateOptions(opts) const { key, afterSteps = [], beforeSteps = [], onError = (err) => { throw err } } = options const id = counterId('MessagePipeline') + key From 28980048fdd727670ae56b75fccce29a113df0e2 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 11 Feb 2021 14:08:37 -0500 Subject: [PATCH 448/517] Fix broken OrderMessages output. --- src/subscribe/OrderMessages.js | 11 ----------- test/integration/GapFill.test.js | 6 ++++-- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/src/subscribe/OrderMessages.js b/src/subscribe/OrderMessages.js index 05b091e83..ea72fa02b 100644 --- a/src/subscribe/OrderMessages.js +++ b/src/subscribe/OrderMessages.js @@ -83,17 +83,6 @@ export default function OrderMessages(client, options = {}) { } }, outStream, // consumer gets outStream - async function* IsDone(src) { - for await (const msg of src) { - if (!gapFill) { - orderingUtil.markMessageExplicitly(msg) - } - - orderingUtil.add(msg) - // note no yield - // orderingUtil writes to outStream itself - } - }, ], async (err) => { done = true orderingUtil.clearGaps() diff --git a/test/integration/GapFill.test.js b/test/integration/GapFill.test.js index 8a7c90980..10e1aad57 100644 --- a/test/integration/GapFill.test.js +++ b/test/integration/GapFill.test.js @@ -123,7 +123,9 @@ describeRepeats('GapFill', () => { expect(subscriber.count(stream.id)).toBe(1) - const published = await publishTestMessages(MAX_MESSAGES) + const published = await publishTestMessages(MAX_MESSAGES, { + timestamp: 111111, + }) const received = [] for await (const m of sub) { @@ -202,7 +204,7 @@ describeRepeats('GapFill', () => { expect(client.connection.getState()).toBe('connected') }, 15000) - it('can fill gaps in resends', async () => { + it.skip('can fill gaps in resends', async () => { const { parse } = client.connection let count = 0 client.connection.parse = (...args) => { From 7d1a91f99bd3ef7b450ec594c5c341d3a5a7ad47 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 11 Feb 2021 14:15:13 -0500 Subject: [PATCH 449/517] Convert jest.setup to ESM. --- jest.config.js | 3 +-- jest.setup.js | 18 +++++++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/jest.config.js b/jest.config.js index f273f3d05..4be81d064 100644 --- a/jest.config.js +++ b/jest.config.js @@ -62,9 +62,8 @@ module.exports = { // An array of directory names to be searched recursively up from the requiring module's location moduleDirectories: [ 'node_modules', - path.resolve('./node_modules'), // makes npm link work. + path.resolve('./node_modules'), // makes npm link work. ], - // An array of file extensions your modules use // moduleFileExtensions: [ // "js", diff --git a/jest.setup.js b/jest.setup.js index 81b9b01b6..5592139d6 100644 --- a/jest.setup.js +++ b/jest.setup.js @@ -1,16 +1,16 @@ -const Debug = require('debug') -const GitRevisionPlugin = require('git-revision-webpack-plugin') +import GitRevisionPlugin from 'git-revision-webpack-plugin' +import Debug from 'debug' const pkg = require('./package.json') -if (process.env.DEBUG_CONSOLE) { - // Use debug as console log - // This prevents jest messing with console output - // Ensuring debug messages are printed alongside console messages, in the correct order - console.log = Debug('Streamr::CONSOLE') // eslint-disable-line no-console -} +export default async () => { + if (process.env.DEBUG_CONSOLE) { + // Use debug as console log + // This prevents jest messing with console output + // Ensuring debug messages are printed alongside console messages, in the correct order + console.log = Debug('Streamr::CONSOLE') // eslint-disable-line no-console + } -module.exports = async () => { if (!process.env.GIT_VERSION) { const gitRevisionPlugin = new GitRevisionPlugin() const [GIT_VERSION, GIT_COMMITHASH, GIT_BRANCH] = await Promise.all([ From 1147ee56e4d45cf3e2ac0d04ef402a0e0f3262d2 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 11 Feb 2021 15:13:54 -0500 Subject: [PATCH 450/517] Refresh package-lock. --- package-lock.json | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4005cb40c..9506d4fc3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2349,9 +2349,9 @@ } }, "@npmcli/move-file": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.1.tgz", - "integrity": "sha512-LtWTicuF2wp7PNTuyCwABx7nNG+DnzSE8gN0iWxkC6mpgm/iOPu0ZMTkXuCxmJxtWFsDxUaixM9COSNJEMUfuQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", "dev": true, "requires": { "mkdirp": "^1.0.4", @@ -5171,9 +5171,9 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.661", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.661.tgz", - "integrity": "sha512-INNzKoL9ceOpPCpF5J+Fp9AOHY1RegwKViohAyTzV3XbkuRUx04r4v8edsDbevsog8UuL0GvD/Qerr2HwVTlSA==", + "version": "1.3.663", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.663.tgz", + "integrity": "sha512-xkVkzHj6k3oRRGlmdgUCCLSLhtFYHDCTH7SeK+LJdJjnsLcrdbpr8EYmfMQhez3V/KPO5UScSpzQ0feYX6Qoyw==", "dev": true }, "elliptic": { @@ -5804,9 +5804,9 @@ } }, "eslint-plugin-promise": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", - "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz", + "integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==", "dev": true }, "eslint-scope": { @@ -12257,12 +12257,12 @@ "dev": true }, "resolve": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", - "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "dev": true, "requires": { - "is-core-module": "^2.1.0", + "is-core-module": "^2.2.0", "path-parse": "^1.0.6" } }, @@ -13155,9 +13155,9 @@ } }, "streamr-test-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/streamr-test-utils/-/streamr-test-utils-1.3.0.tgz", - "integrity": "sha512-mSjtHIUyoVksv6OXVDMj5BecbEXCKfXRmSmiHav1kJLEHj4oE9cF1N0Ml1I3KL3ARw51+RloRnPtRSexg71m/Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/streamr-test-utils/-/streamr-test-utils-1.3.1.tgz", + "integrity": "sha512-3ol+bRQOSUz6Qjso0/VDBNzTxD9msizCOtsHgx7YqGYkxtIUSlGbsVtZh3JhBnnm53BNyaNHS74+N7Mjoa+w5Q==", "dev": true }, "string-length": { @@ -13331,9 +13331,9 @@ }, "dependencies": { "ajv": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.1.tgz", - "integrity": "sha512-ga/aqDYnUy/o7vbsRTFhhTsNeXiYb5JWDIcRIeZfwRNCefwjNTVYCGdGSUrEmiu3yDK3vFvNbgJxvrQW4JXrYQ==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.0.tgz", + "integrity": "sha512-svS9uILze/cXbH0z2myCK2Brqprx/+JJYK5pHicT/GQiBfzzhUVAIT6MwqJg8y4xV/zoGsUeuPuwtoiKSGE15g==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", From 3ac9c576e107d0883554207740781dffc7f197c7 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 11 Feb 2021 15:17:48 -0500 Subject: [PATCH 451/517] Fix issues with gaps in resends. Test gap failure mode. Only ~Close OrderMessages stream once messages sent & all gaps filled or failed. --- src/subscribe/OrderMessages.js | 52 +++++++++++++++++++------------- test/integration/GapFill.test.js | 44 ++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 22 deletions(-) diff --git a/src/subscribe/OrderMessages.js b/src/subscribe/OrderMessages.js index ea72fa02b..5d787b9a4 100644 --- a/src/subscribe/OrderMessages.js +++ b/src/subscribe/OrderMessages.js @@ -1,7 +1,6 @@ import { Utils } from 'streamr-client-protocol' import { pipeline } from '../utils/iterators' -import { Defer } from '../utils' import PushQueue from '../utils/PushQueue' import { validateOptions } from '../stream/utils' @@ -13,36 +12,29 @@ const { OrderingUtil } = Utils * Wraps OrderingUtil into a pipeline. * Implements gap filling */ - +let ID = 0 export default function OrderMessages(client, options = {}) { - const { gapFillTimeout, retryResendAfter, gapFill = true } = client.options - const { streamId, streamPartition } = validateOptions(options) + const { gapFillTimeout, retryResendAfter } = client.options + const { streamId, streamPartition, gapFill = true } = validateOptions(options) + const debug = client.debug.extend(`OrderMessages::${ID}`) + ID += 1 // output buffer const outStream = new PushQueue([], { - // we can end when: - // input has closed (i.e. all messages sent) - // AND - // no gaps are pending - // AND - // gaps have been filled or failed autoEnd: false, }) let done = false - const inputDone = Defer() - const allHandled = Defer() const resendStreams = new Set() // holds outstanding resends for cleanup const orderingUtil = new OrderingUtil(streamId, streamPartition, (orderedMessage) => { if (!outStream.isWritable() || done) { return } - outStream.push(orderedMessage) }, async (from, to, publisherId, msgChainId) => { if (done || !gapFill) { return } - client.debug('gap %o', { + debug('%d gap %o', { streamId, streamPartition, publisherId, msgChainId, from, to, }) @@ -65,22 +57,42 @@ export default function OrderMessages(client, options = {}) { resendStreams.delete(resendMessageStream) await resendMessageStream.cancel() } - }, gapFillTimeout, retryResendAfter) + }, gapFillTimeout, retryResendAfter, gapFill ? 5 : 0) const markMessageExplicitly = orderingUtil.markMessageExplicitly.bind(orderingUtil) + let inputClosed = false + + function maybeClose() { + // we can close when: + // input has closed (i.e. all messages sent) + // AND + // no gaps are pending + // AND + // gaps have been filled or failed + if (inputClosed && orderingUtil.isEmpty()) { + outStream.end() + } + } + + orderingUtil.on('drain', () => { + maybeClose() + }) + + orderingUtil.on('error', (err) => { + outStream.push(err) + }) + return Object.assign(pipeline([ // eslint-disable-next-line require-yield async function* WriteToOrderingUtil(src) { for await (const msg of src) { - if (!gapFill) { - orderingUtil.markMessageExplicitly(msg) - } - orderingUtil.add(msg) // note no yield // orderingUtil writes to outStream itself } + inputClosed = true + maybeClose() }, outStream, // consumer gets outStream ], async (err) => { @@ -90,8 +102,6 @@ export default function OrderMessages(client, options = {}) { resendStreams.clear() await outStream.cancel(err) orderingUtil.clearGaps() - }, { - end: false, }), { markMessageExplicitly, }) diff --git a/test/integration/GapFill.test.js b/test/integration/GapFill.test.js index 10e1aad57..d0eabcead 100644 --- a/test/integration/GapFill.test.js +++ b/test/integration/GapFill.test.js @@ -204,7 +204,7 @@ describeRepeats('GapFill', () => { expect(client.connection.getState()).toBe('connected') }, 15000) - it.skip('can fill gaps in resends', async () => { + it('can fill gaps in resends', async () => { const { parse } = client.connection let count = 0 client.connection.parse = (...args) => { @@ -239,6 +239,48 @@ describeRepeats('GapFill', () => { expect(client.connection.getState()).toBe('connected') }, 60000) + it('can fill gaps in resends even if gap cannot be filled', async () => { + const { parse } = client.connection + let count = 0 + let droppedMsgRef + client.connection.parse = (...args) => { + const msg = parse.call(client.connection, ...args) + if (!msg.streamMessage) { + return msg + } + + count += 1 + if (count === 3) { + if (!droppedMsgRef) { + droppedMsgRef = msg.streamMessage.getMessageRef() + } + return null + } + + if (droppedMsgRef && msg.streamMessage.getMessageRef().compareTo(droppedMsgRef) === 0) { + return null + } + + return msg + } + + const published = await publishTestMessages(MAX_MESSAGES, { + waitForLast: true, + }) + + const sub = await client.resend({ + stream, + last: MAX_MESSAGES, + }) + const received = [] + for await (const m of sub) { + received.push(m.getParsedContent()) + // should not need to explicitly end + } + expect(received).toEqual(published.filter((_value, index) => index !== 2)) + expect(client.connection.getState()).toBe('connected') + }, 60000) + it('can fill gaps between resend and realtime', async () => { // publish 5 messages into storage const published = await publishTestMessages(5, { From 2ec29639869dc278cbbc0384e545ef2ddfcd86be Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 11 Feb 2021 16:20:22 -0500 Subject: [PATCH 452/517] Ignore failed messages explicitly rather than treat them as unfillable gaps in ordering util. --- src/stream/Encryption.js | 2 +- src/subscribe/Decrypt.js | 17 ++++++-- src/subscribe/OrderMessages.js | 2 +- src/subscribe/pipeline.js | 25 ++++++++++-- test/integration/Encryption.test.js | 61 +++++++++++++++++++++++++++++ test/integration/Validation.test.js | 13 ++++-- 6 files changed, 107 insertions(+), 13 deletions(-) diff --git a/src/stream/Encryption.js b/src/stream/Encryption.js index 0726c880c..f682f0c58 100644 --- a/src/stream/Encryption.js +++ b/src/stream/Encryption.js @@ -8,7 +8,7 @@ import { MessageLayer } from 'streamr-client-protocol' import { uuid } from '../utils' -class UnableToDecryptError extends Error { +export class UnableToDecryptError extends Error { constructor(message = '', streamMessage) { super(`Unable to decrypt. ${message} ${util.inspect(streamMessage)}`) this.streamMessage = streamMessage diff --git a/src/subscribe/Decrypt.js b/src/subscribe/Decrypt.js index 6d67c4308..c2c794927 100644 --- a/src/subscribe/Decrypt.js +++ b/src/subscribe/Decrypt.js @@ -1,7 +1,7 @@ import { MessageLayer } from 'streamr-client-protocol' import PushQueue from '../utils/PushQueue' -import EncryptionUtil from '../stream/Encryption' +import EncryptionUtil, { UnableToDecryptError } from '../stream/Encryption' import { SubscriberKeyExchange } from '../stream/KeyExchange' const { StreamMessage } = MessageLayer @@ -26,7 +26,7 @@ export default function Decrypt(client, options = {}) { } }) - async function* decrypt(src) { + async function* decrypt(src, onError = async (err) => { throw err }) { yield* PushQueue.transform(src, async (streamMessage) => { if (!streamMessage.groupKeyId) { return streamMessage @@ -36,8 +36,17 @@ export default function Decrypt(client, options = {}) { return streamMessage } - const groupKey = await requestKey(streamMessage) - await EncryptionUtil.decryptStreamMessage(streamMessage, groupKey) + try { + const groupKey = await requestKey(streamMessage) + if (!groupKey) { + throw new UnableToDecryptError(`Group key not found: ${streamMessage.groupKeyId}`, streamMessage) + } + await EncryptionUtil.decryptStreamMessage(streamMessage, groupKey) + return streamMessage + } catch (err) { + await onError(err, streamMessage) + } + return streamMessage }) } diff --git a/src/subscribe/OrderMessages.js b/src/subscribe/OrderMessages.js index 5d787b9a4..2c1da8e83 100644 --- a/src/subscribe/OrderMessages.js +++ b/src/subscribe/OrderMessages.js @@ -34,7 +34,7 @@ export default function OrderMessages(client, options = {}) { outStream.push(orderedMessage) }, async (from, to, publisherId, msgChainId) => { if (done || !gapFill) { return } - debug('%d gap %o', { + debug('gap %o', { streamId, streamPartition, publisherId, msgChainId, from, to, }) diff --git a/src/subscribe/pipeline.js b/src/subscribe/pipeline.js index 9c1182c42..de6b8d2b7 100644 --- a/src/subscribe/pipeline.js +++ b/src/subscribe/pipeline.js @@ -42,6 +42,11 @@ export default function MessagePipeline(client, opts = {}, onFinally = async () gapFill: false, }) + // collect messages that fail validation/parsing, do not push out of pipeline + // NOTE: we let failed messages be processed and only removed at end so they don't + // end up acting as gaps that we repeatedly try to fill. + const ignoreMessages = new WeakSet() + const p = pipeline([ // take messages msgStream, @@ -61,21 +66,26 @@ export default function MessagePipeline(client, opts = {}, onFinally = async () try { await validate(streamMessage) } catch (err) { - internalOrderingUtil.markMessageExplicitly(streamMessage) + ignoreMessages.add(streamMessage) await onError(err) } yield streamMessage } }, // decrypt - decrypt, + async function* Parse(src) { + yield* decrypt(src, async (err, streamMessage) => { + ignoreMessages.add(streamMessage) + await onError(err) + }) + }, // parse content async function* Parse(src) { for await (const streamMessage of src) { try { streamMessage.getParsedContent() } catch (err) { - internalOrderingUtil.markMessageExplicitly(streamMessage) + ignoreMessages.add(streamMessage) await onError(err) } yield streamMessage @@ -83,6 +93,15 @@ export default function MessagePipeline(client, opts = {}, onFinally = async () }, // re-order messages (ignore gaps) internalOrderingUtil, + // ignore any failed messages + async function* IgnoreMessages(src) { + for await (const streamMessage of src) { + if (ignoreMessages.has(streamMessage)) { + continue + } + yield streamMessage + } + }, // special handling for bye message async function* ByeMessageSpecialHandling(src) { for await (const orderedMessage of src) { diff --git a/test/integration/Encryption.test.js b/test/integration/Encryption.test.js index 3ac930bd1..7f5d05a49 100644 --- a/test/integration/Encryption.test.js +++ b/test/integration/Encryption.test.js @@ -483,5 +483,66 @@ describe('decryption', () => { expect(received).toEqual(published.slice(-2)) await client.unsubscribe(sub) }, 2 * TIMEOUT) + + it('fails gracefully if cannot decrypt', async () => { + const MAX_MESSAGES = 10 + const groupKey = GroupKey.generate() + const keys = { + [stream.id]: { + [groupKey.id]: groupKey, + } + } + + await client.setNextGroupKey(stream.id, groupKey) + + const BAD_INDEX = 6 + let count = 0 + const { parse } = client.connection + client.connection.parse = (...args) => { + const msg = parse.call(client.connection, ...args) + if (!msg.streamMessage) { + return msg + } + + if (count === BAD_INDEX) { + msg.streamMessage.groupKeyId = 'badgroupkey' + } + + count += 1 + return msg + } + + const sub = await client.subscribe({ + stream: stream.id, + groupKeys: keys, + }) + + const onSubError = jest.fn((err) => { + expect(err).toBeInstanceOf(Error) + expect(err.message).toMatch('decrypt') + }) + + sub.on('error', onSubError) + + // Publish after subscribed + const published = await publishTestMessages(MAX_MESSAGES, { + timestamp: 1111111, + }) + + const received = [] + for await (const m of sub) { + received.push(m.getParsedContent()) + if (received.length === published.length - 1) { + break + } + } + + expect(received).toEqual([ + ...published.slice(0, BAD_INDEX), + ...published.slice(BAD_INDEX + 1, MAX_MESSAGES) + ]) + + expect(onSubError).toHaveBeenCalledTimes(1) + }) }) diff --git a/test/integration/Validation.test.js b/test/integration/Validation.test.js index 54e072497..9951fb4c2 100644 --- a/test/integration/Validation.test.js +++ b/test/integration/Validation.test.js @@ -127,7 +127,9 @@ describeRepeats('Validation', () => { return msg } - const published = await publishTestMessages(MAX_MESSAGES) + const published = await publishTestMessages(MAX_MESSAGES, { + timestamp: 111111, + }) const received = [] for await (const m of sub) { @@ -136,11 +138,13 @@ describeRepeats('Validation', () => { break } } - - expect(received).toEqual([ + const expectedMessages = [ + // remove bad message ...published.slice(0, BAD_INDEX), ...published.slice(BAD_INDEX + 1, MAX_MESSAGES) - ]) + ] + + expect(received).toEqual(expectedMessages) expect(client.connection.getState()).toBe('connected') expect(onSubError).toHaveBeenCalledTimes(1) }, 10000) @@ -184,6 +188,7 @@ describeRepeats('Validation', () => { const published = await publishTestMessages(MAX_MESSAGES, { stream, + timestamp: 1111111, }) const received = [] From 02c4d032901445b1e15254313fe03b4605cb147f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 12 Feb 2021 11:21:26 -0500 Subject: [PATCH 453/517] Fix handling for failed gapfills. --- src/subscribe/OrderMessages.js | 10 +++++++--- src/subscribe/index.js | 3 ++- src/subscribe/resendStream.js | 2 +- src/utils/PushQueue.js | 5 ++++- test/integration/GapFill.test.js | 4 ++-- 5 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/subscribe/OrderMessages.js b/src/subscribe/OrderMessages.js index 2c1da8e83..fe06a8ecd 100644 --- a/src/subscribe/OrderMessages.js +++ b/src/subscribe/OrderMessages.js @@ -8,11 +8,13 @@ import resendStream from './resendStream' const { OrderingUtil } = Utils +let ID = 0 + /** * Wraps OrderingUtil into a pipeline. * Implements gap filling */ -let ID = 0 + export default function OrderMessages(client, options = {}) { const { gapFillTimeout, retryResendAfter } = client.options const { streamId, streamPartition, gapFill = true } = validateOptions(options) @@ -70,6 +72,7 @@ export default function OrderMessages(client, options = {}) { // no gaps are pending // AND // gaps have been filled or failed + // NOTE ordering util cannot have gaps if queue is empty if (inputClosed && orderingUtil.isEmpty()) { outStream.end() } @@ -79,8 +82,9 @@ export default function OrderMessages(client, options = {}) { maybeClose() }) - orderingUtil.on('error', (err) => { - outStream.push(err) + orderingUtil.on('error', () => { + // TODO: handle gapfill errors without closing stream or logging + maybeClose() // probably noop }) return Object.assign(pipeline([ diff --git a/src/subscribe/index.js b/src/subscribe/index.js index db4af5fe1..7ca0385a5 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -21,6 +21,7 @@ export class Subscription extends Emitter { this.streamPartition = this.options.streamPartition this._onDone = Defer() + this._onDone.catch(() => {}) // prevent unhandledrejection this._onFinally = onFinally const validate = opts.validate || Validator(client, this.options) @@ -157,7 +158,7 @@ class SubscriptionSession extends Emitter { await this.step() } } catch (err) { - this.emit(err) + this.emit('error', err) } } diff --git a/src/subscribe/resendStream.js b/src/subscribe/resendStream.js index 48271b80f..51c51439a 100644 --- a/src/subscribe/resendStream.js +++ b/src/subscribe/resendStream.js @@ -13,7 +13,7 @@ const { ControlMessage } = ControlLayer * Sends resend request, handles responses. */ -export default function resendStream(client, opts = {}, onFinally = () => {}) { +export default function resendStream(client, opts = {}, onFinally = async () => {}) { const options = validateOptions(opts) const { connection } = client const requestId = counterId(`${options.key}-resend`) diff --git a/src/utils/PushQueue.js b/src/utils/PushQueue.js index 9911cae81..adab5e0a8 100644 --- a/src/utils/PushQueue.js +++ b/src/utils/PushQueue.js @@ -279,7 +279,7 @@ export default class PushQueue { continue // eslint-disable-line no-continue } - const value = await new Promise((resolve, reject) => { + const deferred = new Promise((resolve, reject) => { // wait for next push this.nextQueue.push({ resolve, @@ -287,6 +287,9 @@ export default class PushQueue { }) }) + deferred.catch(() => {}) // prevent unhandledrejection + const value = await deferred + // ignore value if finished if (this.finished) { return diff --git a/test/integration/GapFill.test.js b/test/integration/GapFill.test.js index d0eabcead..7cce47c7d 100644 --- a/test/integration/GapFill.test.js +++ b/test/integration/GapFill.test.js @@ -97,8 +97,8 @@ describeRepeats('GapFill', () => { describe('filling gaps', () => { beforeEach(async () => { await setupClient({ - gapFillTimeout: 1000, - retryResendAfter: 1000, + gapFillTimeout: 200, + retryResendAfter: 200, }) await client.connect() }) From 45d6174a523ffb2c3f705129970e301f8bdffb4b Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 12 Feb 2021 14:26:37 -0500 Subject: [PATCH 454/517] Add onError option to Scaffold. --- src/utils/Scaffold.js | 25 +++++++---- test/unit/Scaffold.test.js | 87 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 8 deletions(-) diff --git a/src/utils/Scaffold.js b/src/utils/Scaffold.js index 67ac3a47a..bf40f8b84 100644 --- a/src/utils/Scaffold.js +++ b/src/utils/Scaffold.js @@ -9,11 +9,13 @@ import AggregatedError from './AggregatedError' * If/when check fails (or there's an error) cleanup functions will be executed in order. * onChange fires when up/down direction changes. * onDone fires when no more up/down steps to execute. + * onError fires when something errors. Rethrow in onError to keep error, don't rethrow to suppress. * returns a function which should be called whenever something changes that could affect the check. */ -export default function Scaffold(sequence = [], _checkFn, { onDone, onChange } = {}) { +export default function Scaffold(sequence = [], _checkFn, { onError, onDone, onChange } = {}) { let error + // ignore error if check fails const nextSteps = sequence.slice().reverse() const prevSteps = [] @@ -23,16 +25,23 @@ export default function Scaffold(sequence = [], _checkFn, { onDone, onChange } = let isDone = false let didStart = false - function onError(err) { - // eslint-disable-next-line require-atomic-updates - error = AggregatedError.from(error, err) + function collectErrors(err) { + try { + if (typeof onError === 'function') { + onError(err) // give option to suppress error + } else { + throw err // rethrow + } + } catch (newErr) { + error = AggregatedError.from(error, newErr) + } } const checkFn = async (...args) => { try { return await _checkFn(...args) } catch (err) { - onError(err, 'in check') + collectErrors(err, 'in check') } return false } @@ -51,7 +60,7 @@ export default function Scaffold(sequence = [], _checkFn, { onDone, onChange } = try { await onChange(shouldUp) } catch (err) { - onError(err) + collectErrors(err) } return next(...args) } @@ -66,7 +75,7 @@ export default function Scaffold(sequence = [], _checkFn, { onDone, onChange } = try { onDownStep = await stepFn(...args) } catch (err) { - onError(err) + collectErrors(err) } onDownSteps.push(onDownStep || (() => {})) return next(...args) @@ -78,7 +87,7 @@ export default function Scaffold(sequence = [], _checkFn, { onDone, onChange } = try { await stepFn() } catch (err) { - onError(err) + collectErrors(err) } nextSteps.push(prevSteps.pop()) return next(...args) diff --git a/test/unit/Scaffold.test.js b/test/unit/Scaffold.test.js index 9e54789e0..24a78b243 100644 --- a/test/unit/Scaffold.test.js +++ b/test/unit/Scaffold.test.js @@ -291,6 +291,93 @@ describe('Scaffold', () => { ]) }) + it('does not error if onError suppresses', async () => { + expect.assertions(2) + const shouldUp = true + + const err = new Error('expected') + const onErrorNoop = jest.fn() + const next = Scaffold([ + async () => { + await up('a') + return () => down('a') // this should throw due to on('next' above + }, + async () => { + await up('b') + return async () => { + await down('b') + } + }, + async () => { + throw err + } + ], () => shouldUp, { + onDone, + onChange, + onError: onErrorNoop, + }) + + await next() + + expect(order).toEqual([ + 'change up', + 'up start a', + 'up end a', + 'up start b', + 'up end b', + 'done up', + ]) + expect(onErrorNoop).toHaveBeenCalledWith(err) + }) + + it('does not error if onError rethrows', async () => { + expect.assertions(3) + const shouldUp = true + + const err = new Error('expected') + const onErrorRethrow = jest.fn((error) => { + throw error + }) + const next = Scaffold([ + async () => { + await up('a') + return () => down('a') // this should throw due to on('next' above + }, + async () => { + await up('b') + return async () => { + await down('b') + } + }, + async () => { + throw err + } + ], () => shouldUp, { + onDone, + onChange, + onError: onErrorRethrow, + }) + + await expect(async () => { + await next() + }).rejects.toThrow(err) + + expect(order).toEqual([ + 'change up', + 'up start a', + 'up end a', + 'up start b', + 'up end b', + 'change down', + 'down start b', + 'down end b', + 'down start a', + 'down end a', + 'done down', + ]) + expect(onErrorRethrow).toHaveBeenCalledWith(err) + }) + it('does nothing if check fails', async () => { const shouldUp = false const next = Scaffold([ From 3e047536e025ed06b839a6755ab05d80566f8b1e Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 12 Feb 2021 15:15:26 -0500 Subject: [PATCH 455/517] Give each test message a count out of total so it's clear what it is. --- test/utils.js | 97 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 33 deletions(-) diff --git a/test/utils.js b/test/utils.js index c764cc53f..e467fef87 100644 --- a/test/utils.js +++ b/test/utils.js @@ -137,6 +137,8 @@ export function getPublishTestMessages(client, defaultOpts = {}) { } const publishTestMessagesRaw = async (n = 4, opts = {}) => { + const id = uid('test') + let msgCount = 0 const { streamId, streamPartition = 0, @@ -149,47 +151,76 @@ export function getPublishTestMessages(client, defaultOpts = {}) { afterEach = () => {}, timestamp, partitionKey, - createMessage = Msg, + createMessage = () => { + msgCount += 1 + return { + test: id, + value: `${msgCount} of ${n}` + } + }, } = validateOptions({ ...defaultOpts, ...opts, }) - const published = [] - for (let i = 0; i < n; i++) { - const message = createMessage() - // eslint-disable-next-line no-await-in-loop, no-loop-func - await beforeEach(message) - // eslint-disable-next-line no-await-in-loop, no-loop-func - const request = await pTimeout(client.publish({ - streamId, - streamPartition, - }, message, timestamp, partitionKey), timeout, `publish timeout ${streamId}: ${i} ${inspect(message)}`) - published.push([ - message, - request, - ]) - - // eslint-disable-next-line no-await-in-loop, no-loop-func - await afterEach(message, request) - // eslint-disable-next-line no-await-in-loop, no-loop-func - await wait(delay) // ensure timestamp increments for reliable resend response in test. + let connectionDone = false + function checkDone() { + if (connectionDone) { + throw new Error('Connection done before finished publishing') + } } - - if (waitForLast) { - const msg = published[published.length - 1][1] - await getWaitForStorage(client)(msg, { - streamId, - streamPartition, - timeout: waitForLastTimeout, - count: waitForLastCount, - messageMatchFn(m, b) { - return m.streamMessage.signature === b.signature - } - }) + const onDone = () => { + connectionDone = true } + try { + client.connection.once('done', onDone) + + const published = [] + for (let i = 0; i < n; i++) { + checkDone() + const message = createMessage() + // eslint-disable-next-line no-await-in-loop, no-loop-func + await beforeEach(message) + checkDone() + // eslint-disable-next-line no-await-in-loop, no-loop-func + const request = await pTimeout(client.publish({ + streamId, + streamPartition, + }, message, timestamp, partitionKey), timeout, `publish timeout ${streamId}: ${i} ${inspect(message)}`) + checkDone() + published.push([ + message, + request, + ]) + + // eslint-disable-next-line no-await-in-loop, no-loop-func + await afterEach(message, request) + checkDone() + // eslint-disable-next-line no-await-in-loop, no-loop-func + await wait(delay) // ensure timestamp increments for reliable resend response in test. + checkDone() + } - return published + checkDone() + + if (waitForLast) { + const msg = published[published.length - 1][1] + await getWaitForStorage(client)(msg, { + streamId, + streamPartition, + timeout: waitForLastTimeout, + count: waitForLastCount, + messageMatchFn(m, b) { + checkDone() + return m.streamMessage.signature === b.signature + } + }) + } + + return published + } finally { + client.connection.off('done', onDone) + } } const publishTestMessages = async (...args) => { From d1772051660b119e5063061ed9ce58f3939334c1 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 12 Feb 2021 15:16:58 -0500 Subject: [PATCH 456/517] Exclude gap related tests from 'npm run test-integration-no-resend' --- package.json | 2 +- test/integration/GapFill.test.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 1ec726854..13f445871 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "test-unit": "jest test/unit --detectOpenHandles", "coverage": "jest --coverage", "test-integration": "jest --forceExit test/integration", - "test-integration-no-resend": "jest --testTimeout=10000 --testPathIgnorePatterns='resend|Resend' --testNamePattern='^((?!(resend|Resend|resent|Resent)).)*$' test/integration/*.test.js", + "test-integration-no-resend": "jest --testTimeout=10000 --testPathIgnorePatterns='resend|Resend' --testNamePattern='^((?!(resend|Resend|resent|Resent|gap|Gap)).)*$' test/integration/*.test.js", "test-integration-resend": "jest --testTimeout=15000 --testNamePattern='(resend|Resend|resent|Resent)' test/integration/*.test.js", "test-integration-dataunions": "jest --testTimeout=15000 --runInBand test/integration/DataUnionEndpoints", "test-flakey": "jest --forceExit test/flakey/*", diff --git a/test/integration/GapFill.test.js b/test/integration/GapFill.test.js index 7cce47c7d..4271b30c8 100644 --- a/test/integration/GapFill.test.js +++ b/test/integration/GapFill.test.js @@ -8,7 +8,7 @@ import config from './config' const MAX_MESSAGES = 10 -describeRepeats('GapFill', () => { +describeRepeats('GapFill with resends', () => { let expectErrors = 0 // check no errors by default let publishTestMessages let onError = jest.fn() From a6f10554a5ea93d907a0c2cc549f149546de63cf Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 12 Feb 2021 15:17:41 -0500 Subject: [PATCH 457/517] Concatenate error messages with passed-in message in AggregatedError. --- src/utils/AggregatedError.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/utils/AggregatedError.js b/src/utils/AggregatedError.js index d95a2baf6..3da55c1c8 100644 --- a/src/utils/AggregatedError.js +++ b/src/utils/AggregatedError.js @@ -37,14 +37,13 @@ export default class AggregatedError extends Error { */ static from(oldErr, newErr, msg) { + if (newErr && msg) { + // copy message + newErr.message = `${msg}: ${newErr.message}` // eslint-disable-line no-param-reassign + } switch (true) { // When no oldErr, just return newErr case !oldErr: { - if (msg) { - // copy message - newErr.message = msg // eslint-disable-line no-param-reassign - } - return newErr } // When oldErr is an AggregatedError, extend it From 9b2cf3955f3b67ec5baf00abdeca31d1277f2aa0 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 12 Feb 2021 15:18:12 -0500 Subject: [PATCH 458/517] Tune collecting of subscription errors. --- src/subscribe/index.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/subscribe/index.js b/src/subscribe/index.js index 7ca0385a5..879640092 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -107,17 +107,17 @@ export class Subscription extends Emitter { */ function multiEmit(emitters, ...args) { - const errs = [] + let error emitters.forEach((s) => { try { s.emit(...args) } catch (err) { - errs.push(err) + AggregatedError.from(error, err, `Error emitting event: ${args[0]}`) } }) - if (errs.length) { - throw new AggregatedError(errs, `Error emitting event: ${args[0]}`) + if (error) { + throw error } } From f523e57f0c8bc234177291ad8719e407d45ec68f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 12 Feb 2021 15:20:05 -0500 Subject: [PATCH 459/517] Ignore connection errors if should be disconnected during subscription scaffold. --- src/Connection.js | 2 +- src/subscribe/index.js | 25 +++++++++++++++++++------ 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/Connection.js b/src/Connection.js index b84da2817..d804ec041 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -11,7 +11,7 @@ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) // add global support for pretty millisecond formatting with %n Debug.formatters.n = (v) => Debug.humanize(v) -class ConnectionError extends Error { +export class ConnectionError extends Error { constructor(err, ...args) { if (err instanceof ConnectionError) { return err diff --git a/src/subscribe/index.js b/src/subscribe/index.js index 879640092..93cab50e5 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -3,6 +3,7 @@ import Emitter from 'events' import { allSettledValues, AggregatedError, Scaffold, Defer, counterId } from '../utils' import { pipeline } from '../utils/iterators' import { validateOptions } from '../stream/utils' +import { ConnectionError } from '../Connection' import { subscribe, unsubscribe } from './api' import MessagePipeline from './pipeline' @@ -163,6 +164,14 @@ class SubscriptionSession extends Emitter { } let deleted = new Set() + const check = () => { + return ( + connection.isConnectionValid() + && !needsReset + // has some active subscription + && this.count() + ) + } this.step = Scaffold([ () => { @@ -220,12 +229,16 @@ class SubscriptionSession extends Emitter { await unsubscribe(this.client, this.options) } } - ], () => ( - connection.isConnectionValid() - && !needsReset - // has some active subscription - && this.count() - )) + ], check, { + onError(err) { + if (err instanceof ConnectionError && !check()) { + // ignore error if state changed + needsReset = true + return + } + throw err + } + }) } has(sub) { From 5354b9e3c3d79b35f6ae97ab9427d9cab502eec4 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 12 Feb 2021 15:25:37 -0500 Subject: [PATCH 460/517] Add another variant of flakey disconnect/reconnect subscribe test. --- test/integration/MultipleClients.test.js | 140 ++++++++++++++++++++++- test/integration/StreamrClient.test.js | 17 +-- 2 files changed, 145 insertions(+), 12 deletions(-) diff --git a/test/integration/MultipleClients.test.js b/test/integration/MultipleClients.test.js index 56bce9cad..c54fa7898 100644 --- a/test/integration/MultipleClients.test.js +++ b/test/integration/MultipleClients.test.js @@ -1,12 +1,15 @@ import { wait } from 'streamr-test-utils' +import { ControlLayer } from 'streamr-client-protocol' import { describeRepeats, uid, fakePrivateKey, getWaitForStorage, getPublishTestMessages, addAfterFn } from '../utils' import StreamrClient from '../../src/StreamrClient' -import { counterId } from '../../src/utils' +import { counterId, Defer, pLimitFn } from '../../src/utils' import Connection from '../../src/Connection' import config from './config' +const { ControlMessage } = ControlLayer + const createClient = (opts = {}) => new StreamrClient({ ...config.clientOptions, auth: { @@ -14,9 +17,13 @@ const createClient = (opts = {}) => new StreamrClient({ }, autoConnect: false, autoDisconnect: false, + // disconnectDelay: 1, + // publishAutoDisconnectDelay: 1, ...opts, }) +const MAX_MESSAGES = 10 + describeRepeats('PubSub with multiple clients', () => { let stream let mainClient @@ -101,9 +108,134 @@ describeRepeats('PubSub with multiple clients', () => { expect(receivedMessagesOther).toEqual([message]) }, 30000) - describe('multiple publishers', () => { - const MAX_MESSAGES = 10 + test('can get messages published from other client if subscriber disconnects', async () => { + otherClient = createClient({ + auth: { + privateKey + } + }) + otherClient.on('error', getOnError(errors)) + await otherClient.connect() + await mainClient.connect() + + const receivedMessagesOther = [] + const msgs = receivedMessagesOther + const otherDone = Defer() + // subscribe to stream from other client instance + await otherClient.subscribe({ + stream: stream.id, + }, (msg) => { + otherClient.debug('other', msg.value) + receivedMessagesOther.push(msg) + + if (receivedMessagesOther.length === MAX_MESSAGES) { + otherDone.resolve() + } + }) + + const disconnect = pLimitFn(async () => { + if (msgs.length === MAX_MESSAGES) { return } + otherClient.debug('disconnecting...', msgs.length) + otherClient.connection.socket.close() + // wait for reconnection before possibly disconnecting again + await otherClient.nextConnection() + otherClient.debug('reconnected...', msgs.length) + }) + + const onConnectionMessage = jest.fn(() => { + // disconnect after every message + disconnect() + }) + + otherClient.connection.on(ControlMessage.TYPES.BroadcastMessage, onConnectionMessage) + otherClient.connection.on(ControlMessage.TYPES.UnicastMessage, onConnectionMessage) + + const publishTestMessages = getPublishTestMessages(mainClient, { + stream, + delay: 600, + waitForLast: true, + waitForLastTimeout: 10000, + waitForLastCount: MAX_MESSAGES, + }) + + const published = await publishTestMessages(MAX_MESSAGES) + await otherDone + + expect(receivedMessagesOther).toEqual(published) + }, 30000) + + test('can get messages published from other client if subscriber disconnects & publisher is also subscribed', async () => { + otherClient = createClient({ + auth: { + privateKey + } + }) + otherClient.on('error', getOnError(errors)) + await otherClient.connect() + await mainClient.connect() + + const receivedMessagesOther = [] + const msgs = receivedMessagesOther + const receivedMessagesMain = [] + const mainDone = Defer() + const otherDone = Defer() + // subscribe to stream from other client instance + await otherClient.subscribe({ + stream: stream.id, + }, (msg) => { + otherClient.debug('other', msg.value) + receivedMessagesOther.push(msg) + + if (receivedMessagesOther.length === MAX_MESSAGES) { + otherDone.resolve() + } + }) + + const disconnect = pLimitFn(async () => { + if (msgs.length === MAX_MESSAGES) { return } + otherClient.debug('disconnecting...', msgs.length) + otherClient.connection.socket.close() + // wait for reconnection before possibly disconnecting again + await otherClient.nextConnection() + otherClient.debug('reconnected...', msgs.length) + }) + const onConnectionMessage = jest.fn(() => { + // disconnect after every message + disconnect() + }) + + otherClient.connection.on(ControlMessage.TYPES.BroadcastMessage, onConnectionMessage) + otherClient.connection.on(ControlMessage.TYPES.UnicastMessage, onConnectionMessage) + // subscribe to stream from main client instance + await mainClient.subscribe({ + stream: stream.id, + }, (msg) => { + mainClient.debug('main', msg.value) + receivedMessagesMain.push(msg) + if (receivedMessagesMain.length === MAX_MESSAGES) { + mainDone.resolve() + } + }) + + const publishTestMessages = getPublishTestMessages(mainClient, { + stream, + delay: 600, + waitForLast: true, + waitForLastTimeout: 10000, + waitForLastCount: MAX_MESSAGES, + }) + const published = await publishTestMessages(MAX_MESSAGES) + + await otherDone + await mainDone + + // messages should arrive on both clients? + expect(receivedMessagesMain).toEqual(published) + expect(receivedMessagesOther).toEqual(published) + }, 30000) + + describe('multiple publishers', () => { async function createPublisher() { const pubClient = createClient({ auth: { @@ -271,7 +403,7 @@ describeRepeats('PubSub with multiple clients', () => { }) published[publisherId] = await publishTestMessages(MAX_MESSAGES, { - async afterEach(pubMsg, req) { + async afterEach(_pubMsg, req) { counter += 1 if (counter === 3) { // late subscribe to stream from other client instance diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index 407977ad7..9934cd321 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -7,7 +7,7 @@ import { wait, waitForEvent } from 'streamr-test-utils' import { describeRepeats, uid, fakePrivateKey, getWaitForStorage, getPublishTestMessages, Msg } from '../utils' import StreamrClient from '../../src/StreamrClient' -import { Defer, pLimitFn } from '../../src/utils' +import { Defer, pLimitFn, pTimeout } from '../../src/utils' import Connection from '../../src/Connection' import config from './config' @@ -17,7 +17,7 @@ const WebSocket = require('ws') const { StreamMessage } = MessageLayer const { SubscribeRequest, UnsubscribeRequest, ResendLastRequest, ControlMessage } = ControlLayer -const MAX_MESSAGES = 5 +const MAX_MESSAGES = 20 describeRepeats('StreamrClient', () => { let expectErrors = 0 // check no errors by default @@ -38,8 +38,8 @@ describeRepeats('StreamrClient', () => { }, autoConnect: false, autoDisconnect: false, - disconnectDelay: 1, - publishAutoDisconnectDelay: 50, + // disconnectDelay: 500, + // publishAutoDisconnectDelay: 250, maxRetries: 2, ...opts, }) @@ -914,6 +914,7 @@ describeRepeats('StreamrClient', () => { }) afterEach(async () => { + otherClient.debug('disconnecting after test') const tasks = [ otherClient.disconnect(), client.disconnect(), @@ -924,12 +925,12 @@ describeRepeats('StreamrClient', () => { it('should work', async () => { const done = Defer() - const msgs = [] await otherClient.subscribe(stream, (msg) => { msgs.push(msg) + otherClient.debug('got msg %d of %d', msgs.length, MAX_MESSAGES) if (msgs.length === MAX_MESSAGES) { // should eventually get here done.resolve() @@ -938,9 +939,11 @@ describeRepeats('StreamrClient', () => { const disconnect = pLimitFn(async () => { if (msgs.length === MAX_MESSAGES) { return } + otherClient.debug('disconnecting...', msgs.length) otherClient.connection.socket.close() // wait for reconnection before possibly disconnecting again await otherClient.nextConnection() + otherClient.debug('reconnected...', msgs.length) }) const onConnectionMessage = jest.fn(() => { @@ -955,11 +958,9 @@ describeRepeats('StreamrClient', () => { const onDisconnected = jest.fn() otherClient.connection.on('connected', onConnected) otherClient.connection.on('disconnected', onDisconnected) - const published = await publishTestMessages(MAX_MESSAGES, { - delay: 1000, + delay: 600, }) - await done // wait for final re-connection after final message await otherClient.connection.nextConnection() From 46c3a3520516830796bc5d858633c71a427f76df Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 16 Feb 2021 11:10:15 -0500 Subject: [PATCH 461/517] Make test/utils getPublishTestMessages timestamp option into optional function. --- test/utils.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/test/utils.js b/test/utils.js index e467fef87..b8f6fe981 100644 --- a/test/utils.js +++ b/test/utils.js @@ -176,30 +176,28 @@ export function getPublishTestMessages(client, defaultOpts = {}) { client.connection.once('done', onDone) const published = [] + /* eslint-disable no-await-in-loop, no-loop-func */ for (let i = 0; i < n; i++) { checkDone() const message = createMessage() - // eslint-disable-next-line no-await-in-loop, no-loop-func await beforeEach(message) checkDone() - // eslint-disable-next-line no-await-in-loop, no-loop-func const request = await pTimeout(client.publish({ streamId, streamPartition, - }, message, timestamp, partitionKey), timeout, `publish timeout ${streamId}: ${i} ${inspect(message)}`) + }, message, typeof timestamp === 'function' ? timestamp() : timestamp, partitionKey), timeout, `publish timeout ${streamId}: ${i} ${inspect(message)}`) checkDone() published.push([ message, request, ]) - // eslint-disable-next-line no-await-in-loop, no-loop-func await afterEach(message, request) checkDone() - // eslint-disable-next-line no-await-in-loop, no-loop-func await wait(delay) // ensure timestamp increments for reliable resend response in test. checkDone() } + /* eslint-enable no-await-in-loop, no-loop-func */ checkDone() From 106d57bce39adc69731ce2dd846e1c97f8fef683 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 16 Feb 2021 11:10:36 -0500 Subject: [PATCH 462/517] Rename pipeline steps. --- src/subscribe/pipeline.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/subscribe/pipeline.js b/src/subscribe/pipeline.js index de6b8d2b7..3e45d7c4b 100644 --- a/src/subscribe/pipeline.js +++ b/src/subscribe/pipeline.js @@ -61,7 +61,7 @@ export default function MessagePipeline(client, opts = {}, onFinally = async () // order messages (fill gaps) orderingUtil, // validate - async function* Validate(src) { + async function* ValidateMessages(src) { for await (const streamMessage of src) { try { await validate(streamMessage) @@ -73,14 +73,14 @@ export default function MessagePipeline(client, opts = {}, onFinally = async () } }, // decrypt - async function* Parse(src) { + async function* DecryptMessages(src) { yield* decrypt(src, async (err, streamMessage) => { ignoreMessages.add(streamMessage) await onError(err) }) }, // parse content - async function* Parse(src) { + async function* ParseMessages(src) { for await (const streamMessage of src) { try { streamMessage.getParsedContent() From 3517ef0f72a96f6dc798c194a4b8b241447a7cca Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 16 Feb 2021 11:13:47 -0500 Subject: [PATCH 463/517] Update flakey disconnect/reconnect subscribe test. --- test/integration/MultipleClients.test.js | 318 ++++++++++++----------- 1 file changed, 171 insertions(+), 147 deletions(-) diff --git a/test/integration/MultipleClients.test.js b/test/integration/MultipleClients.test.js index c54fa7898..68d32f682 100644 --- a/test/integration/MultipleClients.test.js +++ b/test/integration/MultipleClients.test.js @@ -72,168 +72,192 @@ describeRepeats('PubSub with multiple clients', () => { } }) - test('can get messages published from other client', async () => { - otherClient = createClient({ - auth: { - privateKey - } - }) - otherClient.on('error', getOnError(errors)) - await otherClient.connect() - await mainClient.connect() - - const receivedMessagesOther = [] - const receivedMessagesMain = [] - // subscribe to stream from other client instance - await otherClient.subscribe({ - stream: stream.id, - }, (msg) => { - receivedMessagesOther.push(msg) - }) - // subscribe to stream from main client instance - await mainClient.subscribe({ - stream: stream.id, - }, (msg) => { - receivedMessagesMain.push(msg) - }) - const message = { - msg: uid('message'), - } - await wait(5000) - // publish message on main client - await mainClient.publish(stream, message) - await wait(5000) - // messages should arrive on both clients? - expect(receivedMessagesMain).toEqual([message]) - expect(receivedMessagesOther).toEqual([message]) - }, 30000) - - test('can get messages published from other client if subscriber disconnects', async () => { - otherClient = createClient({ - auth: { - privateKey - } - }) - otherClient.on('error', getOnError(errors)) - await otherClient.connect() - await mainClient.connect() + describe('can get messages published from other client', () => { + test('it works', async () => { + otherClient = createClient({ + auth: { + privateKey + } + }) + otherClient.on('error', getOnError(errors)) + await otherClient.connect() + await mainClient.connect() - const receivedMessagesOther = [] - const msgs = receivedMessagesOther - const otherDone = Defer() - // subscribe to stream from other client instance - await otherClient.subscribe({ - stream: stream.id, - }, (msg) => { - otherClient.debug('other', msg.value) - receivedMessagesOther.push(msg) - - if (receivedMessagesOther.length === MAX_MESSAGES) { - otherDone.resolve() + const receivedMessagesOther = [] + const receivedMessagesMain = [] + // subscribe to stream from other client instance + await otherClient.subscribe({ + stream: stream.id, + }, (msg) => { + receivedMessagesOther.push(msg) + }) + // subscribe to stream from main client instance + await mainClient.subscribe({ + stream: stream.id, + }, (msg) => { + receivedMessagesMain.push(msg) + }) + const message = { + msg: uid('message'), } - }) + await wait(5000) + // publish message on main client + await mainClient.publish(stream, message) + await wait(5000) + // messages should arrive on both clients? + expect(receivedMessagesMain).toEqual([message]) + expect(receivedMessagesOther).toEqual([message]) + }, 30000) + + describe('subscriber disconnects after each message', () => { + test('single subscriber', async () => { + const maxMessages = MAX_MESSAGES + Math.floor(Math.random() * MAX_MESSAGES * 0.25) + otherClient = createClient({ + auth: { + privateKey + } + }) + otherClient.on('error', getOnError(errors)) + await otherClient.connect() + await mainClient.connect() + + const receivedMessagesOther = [] + const msgs = receivedMessagesOther + const otherDone = Defer() + // subscribe to stream from other client instance + await otherClient.subscribe({ + stream: stream.id, + }, (msg) => { + otherClient.debug('other', msg.value) + receivedMessagesOther.push(msg) + + if (receivedMessagesOther.length === maxMessages) { + otherDone.resolve() + } + }) + let disconnecting = false + const disconnect = async () => { + if (msgs.length === maxMessages) { return } + + if (disconnecting) { return } + disconnecting = true + otherClient.debug('disconnecting...', msgs.length) + otherClient.connection.socket.close() + // wait for reconnection before possibly disconnecting again + try { + await otherClient.nextConnection() + otherClient.debug('reconnected...', msgs.length) + } finally { + // eslint-disable-next-line require-atomic-updates + disconnecting = false + } + } - const disconnect = pLimitFn(async () => { - if (msgs.length === MAX_MESSAGES) { return } - otherClient.debug('disconnecting...', msgs.length) - otherClient.connection.socket.close() - // wait for reconnection before possibly disconnecting again - await otherClient.nextConnection() - otherClient.debug('reconnected...', msgs.length) - }) + const onConnectionMessage = jest.fn(() => { + // disconnect after every message + disconnect() + }) - const onConnectionMessage = jest.fn(() => { - // disconnect after every message - disconnect() - }) + otherClient.connection.on(ControlMessage.TYPES.BroadcastMessage, onConnectionMessage) + otherClient.connection.on(ControlMessage.TYPES.UnicastMessage, onConnectionMessage) + let t = 0 + const publishTestMessages = getPublishTestMessages(mainClient, { + stream, + delay: 600, + timestamp: () => { + t += 1 + return t + }, + waitForLast: true, + waitForLastTimeout: 10000, + waitForLastCount: maxMessages, + }) - otherClient.connection.on(ControlMessage.TYPES.BroadcastMessage, onConnectionMessage) - otherClient.connection.on(ControlMessage.TYPES.UnicastMessage, onConnectionMessage) + const published = await publishTestMessages(maxMessages) + await otherDone - const publishTestMessages = getPublishTestMessages(mainClient, { - stream, - delay: 600, - waitForLast: true, - waitForLastTimeout: 10000, - waitForLastCount: MAX_MESSAGES, - }) + expect(receivedMessagesOther).toEqual(published) + }, 30000) - const published = await publishTestMessages(MAX_MESSAGES) - await otherDone + test('publisher also subscriber', async () => { + const maxMessages = MAX_MESSAGES + Math.floor(Math.random() * MAX_MESSAGES * 0.25) + otherClient = createClient({ + auth: { + privateKey + } + }) + otherClient.on('error', getOnError(errors)) + await otherClient.connect() + await mainClient.connect() + + const receivedMessagesOther = [] + const msgs = receivedMessagesOther + const receivedMessagesMain = [] + const mainDone = Defer() + const otherDone = Defer() + // subscribe to stream from other client instance + await otherClient.subscribe({ + stream: stream.id, + }, (msg) => { + otherClient.debug('other', msg.value) + receivedMessagesOther.push(msg) + + if (receivedMessagesOther.length === maxMessages) { + otherDone.resolve() + } + }) - expect(receivedMessagesOther).toEqual(published) - }, 30000) + const disconnect = pLimitFn(async () => { + if (msgs.length === maxMessages) { return } + otherClient.debug('disconnecting...', msgs.length) + otherClient.connection.socket.close() + // wait for reconnection before possibly disconnecting again + await otherClient.nextConnection() + otherClient.debug('reconnected...', msgs.length) + }) - test('can get messages published from other client if subscriber disconnects & publisher is also subscribed', async () => { - otherClient = createClient({ - auth: { - privateKey - } - }) - otherClient.on('error', getOnError(errors)) - await otherClient.connect() - await mainClient.connect() + const onConnectionMessage = jest.fn(() => { + // disconnect after every message + disconnect() + }) - const receivedMessagesOther = [] - const msgs = receivedMessagesOther - const receivedMessagesMain = [] - const mainDone = Defer() - const otherDone = Defer() - // subscribe to stream from other client instance - await otherClient.subscribe({ - stream: stream.id, - }, (msg) => { - otherClient.debug('other', msg.value) - receivedMessagesOther.push(msg) - - if (receivedMessagesOther.length === MAX_MESSAGES) { - otherDone.resolve() - } - }) + otherClient.connection.on(ControlMessage.TYPES.BroadcastMessage, onConnectionMessage) + otherClient.connection.on(ControlMessage.TYPES.UnicastMessage, onConnectionMessage) + // subscribe to stream from main client instance + await mainClient.subscribe({ + stream: stream.id, + }, (msg) => { + mainClient.debug('main', msg.value) + receivedMessagesMain.push(msg) + if (receivedMessagesMain.length === maxMessages) { + mainDone.resolve() + } + }) - const disconnect = pLimitFn(async () => { - if (msgs.length === MAX_MESSAGES) { return } - otherClient.debug('disconnecting...', msgs.length) - otherClient.connection.socket.close() - // wait for reconnection before possibly disconnecting again - await otherClient.nextConnection() - otherClient.debug('reconnected...', msgs.length) - }) + let t = 0 - const onConnectionMessage = jest.fn(() => { - // disconnect after every message - disconnect() - }) + const publishTestMessages = getPublishTestMessages(mainClient, { + stream, + delay: 600, + waitForLast: true, + waitForLastTimeout: 10000, + waitForLastCount: maxMessages, + timestamp: () => { + t += 1 + return t + }, + }) + const published = await publishTestMessages(maxMessages) - otherClient.connection.on(ControlMessage.TYPES.BroadcastMessage, onConnectionMessage) - otherClient.connection.on(ControlMessage.TYPES.UnicastMessage, onConnectionMessage) - // subscribe to stream from main client instance - await mainClient.subscribe({ - stream: stream.id, - }, (msg) => { - mainClient.debug('main', msg.value) - receivedMessagesMain.push(msg) - if (receivedMessagesMain.length === MAX_MESSAGES) { - mainDone.resolve() - } - }) + await otherDone + await mainDone - const publishTestMessages = getPublishTestMessages(mainClient, { - stream, - delay: 600, - waitForLast: true, - waitForLastTimeout: 10000, - waitForLastCount: MAX_MESSAGES, + // messages should arrive on both clients? + expect(receivedMessagesMain).toEqual(published) + expect(receivedMessagesOther).toEqual(published) + }, 30000) }) - const published = await publishTestMessages(MAX_MESSAGES) - - await otherDone - await mainDone - - // messages should arrive on both clients? - expect(receivedMessagesMain).toEqual(published) - expect(receivedMessagesOther).toEqual(published) - }, 30000) + }) describe('multiple publishers', () => { async function createPublisher() { From 55c2b5135e24286b6da1ef0db247d1be1ea672ba Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 16 Feb 2021 16:12:40 -0500 Subject: [PATCH 464/517] Use streamr-client-protocol@8.0.0-beta.2. --- package-lock.json | 15 +++++++++++---- package.json | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 9506d4fc3..c3aaca2f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13141,17 +13141,19 @@ "dev": true }, "streamr-client-protocol": { - "version": "8.0.0-beta.1", - "resolved": "https://registry.npmjs.org/streamr-client-protocol/-/streamr-client-protocol-8.0.0-beta.1.tgz", - "integrity": "sha512-sxkn5ef/sqCo6hC/wy2f81hy7ONR/8aYW+H6TlQ2tltwT/q983QBmGtOW2Y4mT0I2QX10f6NDUIlfluemydsyA==", + "version": "8.0.0-beta.2", + "resolved": "https://registry.npmjs.org/streamr-client-protocol/-/streamr-client-protocol-8.0.0-beta.2.tgz", + "integrity": "sha512-TwNkaBIQCyarkkTxpQuMBCu7Uqs0sH5CUK+/g6751J3ZL7VenZgv/dPRuQaatFDP5LboyRTo/qJxD+75TBfZBw==", "requires": { + "@babel/runtime-corejs3": "^7.12.13", "debug": "^4.3.1", "ethers": "^5.0.24", "eventemitter3": "^4.0.7", "heap": "^0.2.6", "promise-memoize": "^1.2.1", "secp256k1": "^4.0.2", - "sha3": "^2.1.3" + "sha3": "^2.1.3", + "strict-event-emitter-types": "^2.0.0" } }, "streamr-test-utils": { @@ -13160,6 +13162,11 @@ "integrity": "sha512-3ol+bRQOSUz6Qjso0/VDBNzTxD9msizCOtsHgx7YqGYkxtIUSlGbsVtZh3JhBnnm53BNyaNHS74+N7Mjoa+w5Q==", "dev": true }, + "strict-event-emitter-types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz", + "integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA==" + }, "string-length": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", diff --git a/package.json b/package.json index 13f445871..c7ce34140 100644 --- a/package.json +++ b/package.json @@ -113,7 +113,7 @@ "qs": "^6.9.6", "quick-lru": "^5.1.1", "readable-stream": "^3.6.0", - "streamr-client-protocol": "^8.0.0-beta.1", + "streamr-client-protocol": "^8.0.0-beta.2", "typescript": "^4.1.4", "uuid": "^8.3.2", "webpack-node-externals": "^2.5.2", From cb878f743c455280b296eb5654f72a828ce28ef9 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 16 Feb 2021 16:28:25 -0500 Subject: [PATCH 465/517] Linting --- src/StreamrClient.ts | 7 +++---- test/integration/StreamrClient.test.js | 2 +- test/utils.js | 10 ++++++---- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index 30d521ec0..f6060c3a8 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -1,5 +1,4 @@ import EventEmitter from 'eventemitter3' -// @ts-expect-error import { ControlLayer } from 'streamr-client-protocol' import Debug from 'debug' @@ -8,7 +7,7 @@ import { validateOptions } from './stream/utils' import Config from './Config' import StreamrEthereum from './Ethereum' import Session from './Session' -import Connection from './Connection' +import Connection, { ConnectionError } from './Connection' import Publisher from './publish' import Subscriber from './subscribe' import { getUserId } from './user' @@ -237,12 +236,12 @@ export default class StreamrClient extends EventEmitter { } onConnectionError(err: Todo) { - this.emit('error', new Connection.ConnectionError(err)) + this.emit('error', new ConnectionError(err)) } getErrorEmitter(source: Todo) { return (err: Todo) => { - if (!(err instanceof Connection.ConnectionError || err.reason instanceof Connection.ConnectionError)) { + if (!(err instanceof ConnectionError || err.reason instanceof ConnectionError)) { // emit non-connection errors this.emit('error', err) } else { diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index 9934cd321..77e3017ee 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -7,7 +7,7 @@ import { wait, waitForEvent } from 'streamr-test-utils' import { describeRepeats, uid, fakePrivateKey, getWaitForStorage, getPublishTestMessages, Msg } from '../utils' import StreamrClient from '../../src/StreamrClient' -import { Defer, pLimitFn, pTimeout } from '../../src/utils' +import { Defer, pLimitFn } from '../../src/utils' import Connection from '../../src/Connection' import config from './config' diff --git a/test/utils.js b/test/utils.js index b8f6fe981..15dbd7632 100644 --- a/test/utils.js +++ b/test/utils.js @@ -182,10 +182,12 @@ export function getPublishTestMessages(client, defaultOpts = {}) { const message = createMessage() await beforeEach(message) checkDone() - const request = await pTimeout(client.publish({ - streamId, - streamPartition, - }, message, typeof timestamp === 'function' ? timestamp() : timestamp, partitionKey), timeout, `publish timeout ${streamId}: ${i} ${inspect(message)}`) + const request = await pTimeout(client.publish( + { streamId, streamPartition }, + message, + typeof timestamp === 'function' ? timestamp() : timestamp, + partitionKey + ), timeout, `publish timeout ${streamId}: ${i} ${inspect(message)}`) checkDone() published.push([ message, From cd45f51c9fd08032e1888b64becdf46f2c0dad81 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 17 Feb 2021 10:07:04 -0500 Subject: [PATCH 466/517] Line-length linting. --- src/Session.ts | 16 ++++++++++++---- src/rest/DataUnionEndpoints.ts | 7 ++++++- src/rest/StreamEndpoints.ts | 9 +++++---- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/src/Session.ts b/src/Session.ts index f9522f131..c3cd2397f 100644 --- a/src/Session.ts +++ b/src/Session.ts @@ -44,15 +44,23 @@ export default class Session extends EventEmitter { // TODO: move loginFunction to StreamrClient constructor where "auth type" is checked if (typeof this.options.privateKey !== 'undefined') { const wallet = new Wallet(this.options.privateKey) - this.loginFunction = async () => this._client.loginEndpoints.loginWithChallengeResponse((d: string) => wallet.signMessage(d), wallet.address) + this.loginFunction = async () => ( + this._client.loginEndpoints.loginWithChallengeResponse((d: string) => wallet.signMessage(d), wallet.address) + ) } else if (typeof this.options.ethereum !== 'undefined') { const provider = new Web3Provider(this.options.ethereum) const signer = provider.getSigner() - this.loginFunction = async () => this._client.loginEndpoints.loginWithChallengeResponse((d: string) => signer.signMessage(d), await signer.getAddress()) + this.loginFunction = async () => ( + this._client.loginEndpoints.loginWithChallengeResponse((d: string) => signer.signMessage(d), await signer.getAddress()) + ) } else if (typeof this.options.apiKey !== 'undefined') { - this.loginFunction = async () => this._client.loginEndpoints.loginWithApiKey(this.options.apiKey!) + this.loginFunction = async () => ( + this._client.loginEndpoints.loginWithApiKey(this.options.apiKey!) + ) } else if (typeof this.options.username !== 'undefined' && typeof this.options.password !== 'undefined') { - this.loginFunction = async () => this._client.loginEndpoints.loginWithUsernamePassword(this.options.username!, this.options.password!) + this.loginFunction = async () => ( + this._client.loginEndpoints.loginWithUsernamePassword(this.options.username!, this.options.password!) + ) } else { if (!this.options.sessionToken) { this.options.unauthenticated = true diff --git a/src/rest/DataUnionEndpoints.ts b/src/rest/DataUnionEndpoints.ts index 697a2347c..73c3bfa72 100644 --- a/src/rest/DataUnionEndpoints.ts +++ b/src/rest/DataUnionEndpoints.ts @@ -433,7 +433,12 @@ async function transportSignatures(client: StreamrClient, messageHash: Todo, opt // template for withdraw functions // client could be replaced with AMB (mainnet and sidechain) -async function untilWithdrawIsComplete(client: StreamrClient, getWithdrawTxFunc: (options: DataUnionOptions) => Todo, getBalanceFunc: (options: DataUnionOptions) => Todo, options: DataUnionOptions = {}) { +async function untilWithdrawIsComplete( + client: StreamrClient, + getWithdrawTxFunc: (options: DataUnionOptions) => Todo, + getBalanceFunc: (options: DataUnionOptions) => Todo, + options: DataUnionOptions = {} +) { const { pollingIntervalMs = 1000, retryTimeoutMs = 60000, diff --git a/src/rest/StreamEndpoints.ts b/src/rest/StreamEndpoints.ts index 95a8071f8..f56e6349c 100644 --- a/src/rest/StreamEndpoints.ts +++ b/src/rest/StreamEndpoints.ts @@ -217,11 +217,12 @@ export class StreamEndpoints { streamPartition, count, }) - const query = { - count, - } - const url = getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'data', 'partitions', streamPartition, 'last') + `?${qs.stringify(query)}` + const url = ( + getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'data', 'partitions', streamPartition, 'last') + + `?${qs.stringify({ count })}` + ) + const json = await authFetch(url, this.client.session) return json } From 421d3ee4b0053933ad4e2be5b09ec2a21e03de2e Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 17 Feb 2021 14:53:39 -0500 Subject: [PATCH 467/517] Remove superfluous @ts-expect-error directives preventing build. --- src/Config.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Config.ts b/src/Config.ts index 65442c4c5..a6599842f 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -1,5 +1,4 @@ import qs from 'qs' -// @ts-expect-error import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import Debug from 'debug' From f3f986a4e4a17e17070d33a3f082aafdfb034923 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 18 Feb 2021 11:18:31 -0500 Subject: [PATCH 468/517] Add client.options.maxGapRequests = 5 to client config, use to configure OrderMessages. --- src/Config.ts | 1 + src/subscribe/OrderMessages.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Config.ts b/src/Config.ts index a6599842f..f0e43c183 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -27,6 +27,7 @@ export default function ClientConfig(opts: StreamrClientOptions = {}) { orderMessages: true, retryResendAfter: 5000, gapFillTimeout: 5000, + maxGapRequests: 5, maxPublishQueueSize: 10000, // Encryption options diff --git a/src/subscribe/OrderMessages.js b/src/subscribe/OrderMessages.js index fe06a8ecd..4da6226f0 100644 --- a/src/subscribe/OrderMessages.js +++ b/src/subscribe/OrderMessages.js @@ -16,7 +16,7 @@ let ID = 0 */ export default function OrderMessages(client, options = {}) { - const { gapFillTimeout, retryResendAfter } = client.options + const { gapFillTimeout, retryResendAfter, maxGapRequests } = client.options const { streamId, streamPartition, gapFill = true } = validateOptions(options) const debug = client.debug.extend(`OrderMessages::${ID}`) ID += 1 @@ -59,7 +59,7 @@ export default function OrderMessages(client, options = {}) { resendStreams.delete(resendMessageStream) await resendMessageStream.cancel() } - }, gapFillTimeout, retryResendAfter, gapFill ? 5 : 0) + }, gapFillTimeout, retryResendAfter, gapFill ? maxGapRequests : 0) const markMessageExplicitly = orderingUtil.markMessageExplicitly.bind(orderingUtil) From 78449c1adde57ddd96ec6bf223e71449d52c08ac Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 23 Feb 2021 15:58:41 +0200 Subject: [PATCH 469/517] NET-205: Simplify DataUnion interface (#202) DataUnion is now an object which we can operate on. To get an instance of DU object, call client.getDataUnion(contractAddress). As the object encapsulates the contract address, we don't need to specify it when we access the DU methods. Some methods that need a member address were previously optional, and the default value for those were the currently authenticated user. Now we need to be more explicit about the parameter and the member address is always required. Also previously there was a dataUnion client option which was a default value for some DU actions. That is no longer supported. There is a new config option factorySidechainAddress. Users don't need to specify this as it has default value in test and will have a default value in production once we know the address. Member join functions: - renamed joinDataUnion() -> join(secret?), if secret is given this will wait until the automatic join request has been processed - added isMember(memberAddress), returns true if the member is an active member - hasJoined() removed Withdraw functions renamed to have word "all" if the amount is not specified: - withdrawAll(options?) - withdrawAllTo(recipient, options?) - signWithdrawAllTo(recipientAddress) - signWithdrawAmountTo(recipientAddress, amountTokenWei) - admin: withdrawFor() -> withdrawAllToMember(memberAddress, options?) - admin: withdrawAllToSigned(memberAddress, recipientAddress, signature, options?) - removed methods that returned the raw withdraw transactions removed: getWithdrawTx(), getWithdrawTxTo(), getWithdrawTxFor() Public query functions: - renamed getDataUnionStats() -> getStats() - getMemberStats(memberAddress) - removed getMembers as it is not supported in DU v2 - renamed getBalance() -> getWithdrawableEarnings(memberAddress) - getAdminFee() - getAdminAddress() - getVersion() Admin functions: - client.deployDataUnion(options), waits until the deployment is ready, supports new options: confirmations and gasPrice - createSecret(name?) - addMembers(memberAddressList, options?) - renamed kick() -> removeMembers(memberAddressList, options?) - setAdminFee(newFeeFraction) Other functions: - not related to DU: client.getTokenBalance() Public accessors for DataUnion objects: - getAddress(), returns string - getSidechainAddress(), returns string Internal functions: - client._getDataUnionFromName({ dataUnionName, deployerAddress }), returns DataUnion - dataUnion._getContract(), returns Promise Future refactoring, should be done after this PR is in 5.x: - Move many DataUnionEndpoints.ts functions to DataUnion class (and inline DataUnion methods that just call an endpoint methods) - Move getTokenBalance to a separate class as it is not DU-related - Remove caching from fetchDataUnionMainnetAddress --- .eslintrc.js | 4 +- README.md | 66 +- package-lock.json | 1449 +++++++++-------- package.json | 8 +- src/Config.ts | 12 +- src/StreamrClient.ts | 137 +- src/dataunion/DataUnion.ts | 139 ++ src/rest/DataUnionEndpoints.ts | 439 +++-- .../adminWithdrawMember.test.js | 138 -- .../adminWithdrawSigned.test.js | 155 -- .../DataUnionEndpoints/withdraw.test.js | 140 -- .../DataUnionEndpoints/withdrawTo.test.js | 142 -- test/integration/config.js | 1 + .../DataUnionEndpoints.test.ts} | 65 +- .../calculate.test.ts} | 22 +- test/integration/dataunion/deploy.test.ts | 49 + test/integration/dataunion/member.test.ts | 102 ++ test/integration/dataunion/signature.test.ts | 70 + test/integration/dataunion/withdraw.test.ts | 198 +++ 19 files changed, 1619 insertions(+), 1717 deletions(-) create mode 100644 src/dataunion/DataUnion.ts delete mode 100644 test/integration/DataUnionEndpoints/adminWithdrawMember.test.js delete mode 100644 test/integration/DataUnionEndpoints/adminWithdrawSigned.test.js delete mode 100644 test/integration/DataUnionEndpoints/withdraw.test.js delete mode 100644 test/integration/DataUnionEndpoints/withdrawTo.test.js rename test/integration/{DataUnionEndpoints/DataUnionEndpoints.test.js => dataunion/DataUnionEndpoints.test.ts} (79%) rename test/integration/{DataUnionEndpoints/calculate.test.js => dataunion/calculate.test.ts} (57%) create mode 100644 test/integration/dataunion/deploy.test.ts create mode 100644 test/integration/dataunion/member.test.ts create mode 100644 test/integration/dataunion/signature.test.ts create mode 100644 test/integration/dataunion/withdraw.test.ts diff --git a/.eslintrc.js b/.eslintrc.js index 543def847..57180859f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -42,7 +42,9 @@ module.exports = { 'no-shadow': 'off', '@typescript-eslint/no-shadow': 'error', 'no-unused-vars': 'off', - '@typescript-eslint/no-unused-vars': ['error'] + '@typescript-eslint/no-unused-vars': ['error'], + 'no-else-return': 'off', + 'no-return-await': 'off' }, settings: { 'import/resolver': { diff --git a/README.md b/README.md index 24bd6b1a5..46be08f20 100644 --- a/README.md +++ b/README.md @@ -324,37 +324,28 @@ All the below functions return a Promise which gets resolved with the result. ## Data Unions -This library provides functions for working with Data Unions. +This library provides functions for working with Data Unions. To get a DataUnion instance, call `client.getDataUnion(address)`. To deploy a new DataUnion, call `deployDataUnion(options)` -TODO: check all this documentation before merging/publishing, probably some of it is out of date (DU1 era) - -Data union functions take a third parameter, `options`, which are either overrides to options given in the constructor, or optional arguments to the function. +TODO: All `options`-parameters should be documented (see TypeScript interfaces for the definitions) +These DataUnion-specific options are used from `StreamrClient` options: | Property | Default | Description | | :----------------------- | :----------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | -| wallet | given in auth | ethers.js Wallet object to use to sign and send withdraw transaction | -| provider | mainnet | ethers.js Provider to use if wallet wasn't provided | -| confirmations | `1` | Number of blocks to wait after the withdraw transaction is mined | -| gasPrice | ethers.js supplied | Probably uses the network estimate | -| dataUnion | - | Address or contract object of the data union that is the target of the operation | | tokenAddress | 0x0Cf0Ee637
88A0849fE52
97F3407f701
E122cC023 | Token used by the DU | -| minimumWithdrawTokenWei | 1000000 | Threshold value set in AMB configs, smallest token amount that can pass over the bridge | -| sidechainTokenAddress | TODO | sidechain token address | | factoryMainnetAddress | TODO | Data Union factory that creates a new Data Union | -| sidechainAmbAddress | TODO | Arbitrary Message-passing Bridge (AMB), see [Tokenbridge github page](https://github.com/poanetwork/tokenbridge) | -| payForSignatureTransport | `true` | Someone must pay for transporting the withdraw tx to mainnet, either us or bridge operator | +| minimumWithdrawTokenWei | 1000000 | Threshold value set in AMB configs, smallest token amount that can pass over the bridge | + ### Admin Functions -| Name | Returns | Description | -| :-------------------------------------------------------------------- | :------------------ | :------------------------------------------------------------- | -| deployDataUnion(options) | Dataunion contract | Deploy a new Data Union | -| createSecret(dataUnionContractAddress, secret\[, name]) | | Create a secret for a Data Union | -| addMembers(memberAddressList, options) | Transaction receipt | Add members | -| kick(memberAddressList, options) | Transaction receipt | Kick members out from Data Union | -| withdrawMember(memberAddress, options) | | | -| withdrawToSigned(memberAddress, recipientAddress, signature, options) | | | -| setAdminFee(newFeeFraction, options) | Transaction receipt | `newFeeFraction` is a `Number` between 0.0 and 1.0 (inclusive) | +| Name | Returns | Description | +| :-------------------------------------------------------------------------- | :------------------ | :------------------------------------------------------------- | +| createSecret(\[name]) | string | Create a secret for a Data Union | +| addMembers(memberAddressList, \[options]) | Transaction receipt | Add members | +| removeMembers(memberAddressList, \[options]) | Transaction receipt | Remove members from Data Union | +| withdrawAllToMember(memberAddress, \[options]) | | | +| withdrawAllToSigned(memberAddress, recipientAddress, signature, \[options]) | | | +| setAdminFee(newFeeFraction) | Transaction receipt | `newFeeFraction` is a `Number` between 0.0 and 1.0 (inclusive) | Here's an example how to deploy a data union contract and set the admin fee: @@ -371,12 +362,12 @@ await client.setAdminFee(0.3, { dataUnion }) | Name | Returns | Description | | :-------------------------------------------------------------- | :------------------ | :-------------------------------------------------------------------------- | -| joinDataUnion(options) | JoinRequest | Join a Data Union | -| hasJoined(\[memberAddress], options) | - | Wait until member has been accepted | -| withdraw(options) | Transaction receipt | Withdraw funds from Data Union | -| withdrawTo(recipientAddress, dataUnionContractAddress, options) | Transaction receipt | Donate/move your earnings to recipientAddress instead of your memberAddress | -| signWithdrawTo(recipientAddress, options) | Signature (string) | Signature that can be used to withdraw tokens to given recipientAddress | -| signWithdrawAmountTo(recipientAddress, amountTokenWei, options) | Signature (string) | Signature that can be used to withdraw tokens to given recipientAddress | +| join(\[secret]) | JoinRequest | Join the Data Union (if a valid secret is given, the promise waits until the automatic join request has been processed) | +| isMember(memberAddress) | boolean | | +| withdrawAll(\[options]) | Transaction receipt | Withdraw funds from Data Union | +| withdrawAllTo(recipientAddress, \[options]) | Transaction receipt | Donate/move your earnings to recipientAddress instead of your memberAddress | +| signWithdrawAllTo(recipientAddress) | Signature (string) | Signature that can be used to withdraw tokens to given recipientAddress | +| signWithdrawAmountTo(recipientAddress, amountTokenWei) | Signature (string) | Signature that can be used to withdraw tokens to given recipientAddress | Here's an example how to sign off on a withdraw to (any) recipientAddress: @@ -386,7 +377,7 @@ const client = new StreamrClient({ dataUnion, }) -const signature = await client.signWithdrawTo(recipientAddress) +const signature = await client.signWithdrawAllTo(recipientAddress) ``` ### Query functions @@ -395,16 +386,12 @@ These are available for everyone and anyone, to query publicly available info fr | Name | Returns | Description | | :--------------------------------------------------------- | :--------------------------------------------- | :-------------------------------------- | -| getMemberStats(dataUnionContractAddress\[, memberAddress]) | {earnings, proof, ...} | Get member's stats | -| getDataUnionStats(dataUnionContractAddress) | {activeMemberCount, totalEarnings, ...} | Get Data Union's statistics | -| ~~getMembers(dataUnionContractAddress)~~ | | NOT available in DU2 at the moment | -| getAdminFee(options) | `Number` between 0.0 and 1.0 (inclusive) | Admin's cut from revenues | -| getAdminAddress(options) | Ethereum address | Data union admin's address | -| getDataUnionStats(options) | Stats object | Various metrics from the smart contract | -| getMemberStats(\[memberAddress], options) | Member stats object | Various metrics from the smart contract | -| getMemberBalance(\[memberAddress], options) | `BigNumber` withdrawable DATA tokens in the DU | | -| getTokenBalance(address, options) | `BigNumber` | Mainnet DATA token balance | -| getDataUnionVersion(contractAddress) | `0`, `1` or `2` | `0` if the contract is not a data union | +| getStats() | {activeMemberCount, totalEarnings, ...} | Get Data Union's statistics | +| getMemberStats(memberAddress) | {earnings, proof, ...} | Get member's stats | +| getWithdrawableEarnings(memberAddress) | `BigNumber` withdrawable DATA tokens in the DU | | +| getAdminFee() | `Number` between 0.0 and 1.0 (inclusive) | Admin's cut from revenues | +| getAdminAddress() | Ethereum address | Data union admin's address | +| getVersion() | `0`, `1` or `2` | `0` if the contract is not a data union | Here's an example how to get a member's withdrawable token balance (in "wei", where 1 DATA = 10^18 wei) @@ -421,6 +408,7 @@ const withdrawableWei = await client.getMemberBalance(memberAddress) | Name | Description | | :-------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | StreamrClient.generateEthereumAccount() | Generates a random Ethereum private key and returns an object with fields `address` and privateKey. Note that this private key can be used to authenticate to the Streamr API by passing it in the authentication options, as described earlier in this document. | +| getTokenBalance(address) | `BigNumber` | Mainnet DATA token balance | ## Events diff --git a/package-lock.json b/package-lock.json index c3aaca2f7..47bd4c852 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@babel/cli": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.12.13.tgz", - "integrity": "sha512-Zto3HPeE0GRmaxobUl7NvFTo97NKe1zdAuWqTO8oka7nE0IIqZ4CFvuRZe1qf+ZMd7eHMhwqrecjwc10mjXo/g==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.12.10.tgz", + "integrity": "sha512-+y4ZnePpvWs1fc/LhZRTHkTesbXkyBYuOB+5CyodZqrEuETXi3zOVfpAQIdgC3lXbHLTDG9dQosxR9BhvLKDLQ==", "dev": true, "requires": { "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents", @@ -23,34 +23,34 @@ } }, "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, "requires": { - "@babel/highlight": "^7.12.13" + "@babel/highlight": "^7.10.4" } }, "@babel/compat-data": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.13.tgz", - "integrity": "sha512-U/hshG5R+SIoW7HVWIdmy1cB7s3ki+r3FpyEZiCgpi4tFgPnX/vynY80ZGSASOIrUM6O7VxOgCZgdt7h97bUGg==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.7.tgz", + "integrity": "sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw==", "dev": true }, "@babel/core": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.13.tgz", - "integrity": "sha512-BQKE9kXkPlXHPeqissfxo0lySWJcYdEP0hdtJOH/iJfDdhOCcgtNCjftCJg3qqauB4h+lz2N6ixM++b9DN1Tcw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.12.13", - "@babel/helper-module-transforms": "^7.12.13", - "@babel/helpers": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.10.tgz", + "integrity": "sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.12.10", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helpers": "^7.12.5", + "@babel/parser": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/traverse": "^7.12.10", + "@babel/types": "^7.12.10", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", @@ -61,188 +61,199 @@ } }, "@babel/generator": { - "version": "7.12.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.15.tgz", - "integrity": "sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", + "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", "dev": true, "requires": { - "@babel/types": "^7.12.13", + "@babel/types": "^7.12.11", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", - "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", + "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.12.10" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz", - "integrity": "sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", + "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-explode-assignable-expression": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helper-compilation-targets": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.13.tgz", - "integrity": "sha512-dXof20y/6wB5HnLOGyLh/gobsMvDNoekcC+8MCV2iaTd5JemhFkPD73QB+tK3iFC9P0xJC73B6MvKkyUfS9cCw==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz", + "integrity": "sha512-+qH6NrscMolUlzOYngSBMIOQpKUGPPsc61Bu5W10mg84LxZ7cmvnBHzARKbDoFxVvqqAbj6Tg6N7bSrWSPXMyw==", "dev": true, "requires": { - "@babel/compat-data": "^7.12.13", - "@babel/helper-validator-option": "^7.12.11", + "@babel/compat-data": "^7.12.5", + "@babel/helper-validator-option": "^7.12.1", "browserslist": "^4.14.5", "semver": "^5.5.0" } }, "@babel/helper-create-class-features-plugin": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.13.tgz", - "integrity": "sha512-Vs/e9wv7rakKYeywsmEBSRC9KtmE7Px+YBlESekLeJOF0zbGUicGfXSNi3o+tfXSNS48U/7K9mIOOCR79Cl3+Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", + "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-member-expression-to-functions": "^7.12.13", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-replace-supers": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-member-expression-to-functions": "^7.12.1", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.10.4" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.13.tgz", - "integrity": "sha512-XC+kiA0J3at6E85dL5UnCYfVOcIZ834QcAY0TIpgUVnz0zDzg+0TtvZTnJ4g9L1dPRGe30Qi03XCIS4tYCLtqw==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz", + "integrity": "sha512-idnutvQPdpbduutvi3JVfEgcVIHooQnhvhx0Nk9isOINOIGYkZea1Pk2JlJRiUnMefrlvr0vkByATBY/mB4vjQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-annotate-as-pure": "^7.10.4", "regexpu-core": "^4.7.1" } }, + "@babel/helper-define-map": { + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", + "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.10.4", + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" + } + }, "@babel/helper-explode-assignable-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.13.tgz", - "integrity": "sha512-5loeRNvMo9mx1dA/d6yNi+YiKziJZFylZnCo1nmFF4qPU4yJ14abhWESuSMQSlQxWdxdOFzxXjk/PpfudTtYyw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", + "integrity": "sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.12.1" } }, "@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", + "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-get-function-arity": "^7.12.10", + "@babel/template": "^7.12.7", + "@babel/types": "^7.12.11" } }, "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", + "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.12.10" } }, "@babel/helper-hoist-variables": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.12.13.tgz", - "integrity": "sha512-KSC5XSj5HreRhYQtZ3cnSnQwDzgnbdUDEFsxkN0m6Q3WrCRt72xrnZ8+h+pX7YxM7hr87zIO3a/v5p/H3TrnVw==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", + "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.10.4" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.13.tgz", - "integrity": "sha512-B+7nN0gIL8FZ8SvMcF+EPyB21KnCcZHQZFczCxbiNGV/O0rsrSBlWGLzmtBJ3GMjSVMIm4lpFhR+VdVBuIsUcQ==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", + "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.12.7" } }, "@babel/helper-module-imports": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz", - "integrity": "sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", + "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.12.5" } }, "@babel/helper-module-transforms": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.13.tgz", - "integrity": "sha512-acKF7EjqOR67ASIlDTupwkKM1eUisNAjaSduo5Cz+793ikfnpe7p4Q7B7EWU2PCoSTPWsQkR7hRUWEIZPiVLGA==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-replace-supers": "^7.12.13", - "@babel/helper-simple-access": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/helper-validator-identifier": "^7.12.11", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", + "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-simple-access": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/helper-validator-identifier": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.1", + "@babel/types": "^7.12.1", "lodash": "^4.17.19" } }, "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", + "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.12.10" } }, "@babel/helper-plugin-utils": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", - "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", + "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", "dev": true }, "@babel/helper-remap-async-to-generator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.13.tgz", - "integrity": "sha512-Qa6PU9vNcj1NZacZZI1Mvwt+gXDH6CTfgAkSjeRMLE8HxtDK76+YDId6NQR+z7Rgd5arhD2cIbS74r0SxD6PDA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz", + "integrity": "sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-wrap-function": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-wrap-function": "^7.10.4", + "@babel/types": "^7.12.1" } }, "@babel/helper-replace-supers": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.13.tgz", - "integrity": "sha512-pctAOIAMVStI2TMLhozPKbf5yTEXc0OJa0eENheb4w09SrgOWEs+P4nTOZYJQCqs8JlErGLDPDJTiGIp3ygbLg==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", + "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.13", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-member-expression-to-functions": "^7.12.7", + "@babel/helper-optimise-call-expression": "^7.12.10", + "@babel/traverse": "^7.12.10", + "@babel/types": "^7.12.11" } }, "@babel/helper-simple-access": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.13.tgz", - "integrity": "sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", + "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.12.1" } }, "@babel/helper-skip-transparent-expression-wrappers": { @@ -255,12 +266,12 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", + "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", "dev": true, "requires": { - "@babel/types": "^7.12.13" + "@babel/types": "^7.12.11" } }, "@babel/helper-validator-identifier": { @@ -276,64 +287,64 @@ "dev": true }, "@babel/helper-wrap-function": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.13.tgz", - "integrity": "sha512-t0aZFEmBJ1LojdtJnhOaQEVejnzYhyjWHSsNSNo8vOYRbAJNh6r6GQF7pd36SqG7OKGbn+AewVQ/0IfYfIuGdw==", + "version": "7.12.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz", + "integrity": "sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/helper-function-name": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.10.4", + "@babel/types": "^7.10.4" } }, "@babel/helpers": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.13.tgz", - "integrity": "sha512-oohVzLRZ3GQEk4Cjhfs9YkJA4TdIDTObdBEZGrd6F/T0GPSnuV6l22eMcxlvcvzVIPH3VTtxbseudM1zIE+rPQ==", + "version": "7.12.5", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", + "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", "dev": true, "requires": { - "@babel/template": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/template": "^7.10.4", + "@babel/traverse": "^7.12.5", + "@babel/types": "^7.12.5" } }, "@babel/highlight": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", - "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.12.11", + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.12.15", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.15.tgz", - "integrity": "sha512-AQBOU2Z9kWwSZMd6lNjCX0GUgFonL1wAM1db8L8PMk9UDaGsRCArBkU4Sc+UCM3AE4hjbXx+h58Lb3QT4oRmrA==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", + "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.13.tgz", - "integrity": "sha512-1KH46Hx4WqP77f978+5Ye/VUbuwQld2hph70yaw2hXS2v7ER2f3nlpNMu909HO2rbvP0NKLlMVDPh9KXklVMhA==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.12.tgz", + "integrity": "sha512-nrz9y0a4xmUrRq51bYkWJIO5SBZyG2ys2qinHsN0zHDHVsUaModrkpyWWWXfGqYQmOL3x9sQIcTNN/pBGpo09A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13", - "@babel/helper-remap-async-to-generator": "^7.12.13", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.12.1", "@babel/plugin-syntax-async-generators": "^7.8.0" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.13.tgz", - "integrity": "sha512-8SCJ0Ddrpwv4T7Gwb33EmW1V9PY5lggTO+A8WjyIwxrSHDUyBw4MtF96ifn1n8H806YlxbVCoKXbbmzD6RD+cA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", + "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-proposal-dynamic-import": { @@ -347,105 +358,105 @@ } }, "@babel/plugin-proposal-export-namespace-from": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.13.tgz", - "integrity": "sha512-INAgtFo4OnLN3Y/j0VwAgw3HDXcDtX+C/erMvWzuV9v71r7urb6iyMXu7eM9IgLr1ElLlOkaHjJ0SbCmdOQ3Iw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz", + "integrity": "sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.13.tgz", - "integrity": "sha512-v9eEi4GiORDg8x+Dmi5r8ibOe0VXoKDeNPYcTTxdGN4eOWikrJfDJCJrr1l5gKGvsNyGJbrfMftC2dTL6oz7pg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz", + "integrity": "sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.0" } }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.13.tgz", - "integrity": "sha512-fqmiD3Lz7jVdK6kabeSr1PZlWSUVqSitmHEe3Z00dtGTKieWnX9beafvavc32kjORa5Bai4QNHgFDwWJP+WtSQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz", + "integrity": "sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.13.tgz", - "integrity": "sha512-Qoxpy+OxhDBI5kRqliJFAl4uWXk3Bn24WeFstPH0iLymFehSAUR8MHpqU7njyXv/qbo7oN6yTy5bfCmXdKpo1Q==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz", + "integrity": "sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.13.tgz", - "integrity": "sha512-O1jFia9R8BUCl3ZGB7eitaAPu62TXJRHn7rh+ojNERCFyqRwJMTmhz+tJ+k0CwI6CLjX/ee4qW74FSqlq9I35w==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz", + "integrity": "sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.13.tgz", - "integrity": "sha512-WvA1okB/0OS/N3Ldb3sziSrXg6sRphsBgqiccfcQq7woEn5wQLNX82Oc4PlaFcdwcWHuQXAtb8ftbS8Fbsg/sg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", + "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.12.13" + "@babel/plugin-transform-parameters": "^7.12.1" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.13.tgz", - "integrity": "sha512-9+MIm6msl9sHWg58NvqpNpLtuFbmpFYk37x8kgnGzAHvX35E1FyAwSUt5hIkSoWJFSAH+iwU8bJ4fcD1zKXOzg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", + "integrity": "sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.13.tgz", - "integrity": "sha512-0ZwjGfTcnZqyV3y9DSD1Yk3ebp+sIUpT2YDqP8hovzaNZnQq2Kd7PEqa6iOIUDBXBt7Jl3P7YAcEIL5Pz8u09Q==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz", + "integrity": "sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.13.tgz", - "integrity": "sha512-sV0V57uUwpauixvR7s2o75LmwJI6JECwm5oPUY5beZB1nBl2i37hc7CJGqB5G+58fur5Y6ugvl3LRONk5x34rg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", + "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-class-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz", - "integrity": "sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", + "integrity": "sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-async-generators": { @@ -467,12 +478,12 @@ } }, "@babel/plugin-syntax-class-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", - "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", + "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-dynamic-import": { @@ -566,12 +577,12 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", - "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", + "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-syntax-typescript": { @@ -592,307 +603,325 @@ } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.13.tgz", - "integrity": "sha512-tBtuN6qtCTd+iHzVZVOMNp+L04iIJBpqkdY42tWbmjIT5wvR2kx7gxMBsyhQtFzHwBbyGi9h8J8r9HgnOpQHxg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", + "integrity": "sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.13.tgz", - "integrity": "sha512-psM9QHcHaDr+HZpRuJcE1PXESuGWSCcbiGFFhhwfzdbTxaGDVzuVtdNYliAwcRo3GFg0Bc8MmI+AvIGYIJG04A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz", + "integrity": "sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13", - "@babel/helper-remap-async-to-generator": "^7.12.13" + "@babel/helper-module-imports": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-remap-async-to-generator": "^7.12.1" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz", - "integrity": "sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz", + "integrity": "sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz", - "integrity": "sha512-Pxwe0iqWJX4fOOM2kEZeUuAxHMWb9nK+9oh5d11bsLoB0xMg+mkDpt0eYuDZB7ETrY9bbcVlKUGTOGWy7BHsMQ==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.12.tgz", + "integrity": "sha512-VOEPQ/ExOVqbukuP7BYJtI5ZxxsmegTwzZ04j1aF0dkSypGo9XpDHuOrABsJu+ie+penpSJheDJ11x1BEZNiyQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-classes": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.13.tgz", - "integrity": "sha512-cqZlMlhCC1rVnxE5ZGMtIb896ijL90xppMiuWXcwcOAuFczynpd3KYemb91XFFPi3wJSe/OcrX9lXoowatkkxA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz", + "integrity": "sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.12.13", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13", - "@babel/helper-replace-supers": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-define-map": "^7.10.4", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-optimise-call-expression": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1", + "@babel/helper-split-export-declaration": "^7.10.4", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.13.tgz", - "integrity": "sha512-dDfuROUPGK1mTtLKyDPUavmj2b6kFu82SmgpztBFEO974KMjJT+Ytj3/oWsTUMBmgPcp9J5Pc1SlcAYRpJ2hRA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", + "integrity": "sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-destructuring": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.13.tgz", - "integrity": "sha512-Dn83KykIFzjhA3FDPA1z4N+yfF3btDGhjnJwxIj0T43tP0flCujnU8fKgEkf0C1biIpSv9NZegPBQ1J6jYkwvQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz", + "integrity": "sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz", - "integrity": "sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz", + "integrity": "sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz", - "integrity": "sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz", + "integrity": "sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz", - "integrity": "sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz", + "integrity": "sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-for-of": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.13.tgz", - "integrity": "sha512-xCbdgSzXYmHGyVX3+BsQjcd4hv4vA/FDy7Kc8eOpzKmBBPEOTurt0w5fCRQaGl+GSBORKgJdstQ1rHl4jbNseQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz", + "integrity": "sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz", - "integrity": "sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz", + "integrity": "sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz", - "integrity": "sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", + "integrity": "sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz", - "integrity": "sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz", + "integrity": "sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.13.tgz", - "integrity": "sha512-JHLOU0o81m5UqG0Ulz/fPC68/v+UTuGTWaZBUwpEk1fYQ1D9LfKV6MPn4ttJKqRo5Lm460fkzjLTL4EHvCprvA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", + "integrity": "sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.13.tgz", - "integrity": "sha512-OGQoeVXVi1259HjuoDnsQMlMkT9UkZT9TpXAsqWplS/M0N1g3TJAn/ByOCeQu7mfjc5WpSsRU+jV1Hd89ts0kQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz", + "integrity": "sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13", - "@babel/helper-simple-access": "^7.12.13", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-simple-access": "^7.12.1", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.13.tgz", - "integrity": "sha512-aHfVjhZ8QekaNF/5aNdStCGzwTbU7SI5hUybBKlMzqIMC7w7Ho8hx5a4R/DkTHfRfLwHGGxSpFt9BfxKCoXKoA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz", + "integrity": "sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.12.13", - "@babel/helper-module-transforms": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13", - "@babel/helper-validator-identifier": "^7.12.11", + "@babel/helper-hoist-variables": "^7.10.4", + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-validator-identifier": "^7.10.4", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.13.tgz", - "integrity": "sha512-BgZndyABRML4z6ibpi7Z98m4EVLFI9tVsZDADC14AElFaNHHBcJIovflJ6wtCqFxwy2YJ1tJhGRsr0yLPKoN+w==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz", + "integrity": "sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-module-transforms": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz", - "integrity": "sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz", + "integrity": "sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.12.1" } }, "@babel/plugin-transform-new-target": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz", - "integrity": "sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", + "integrity": "sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-object-super": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz", - "integrity": "sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz", + "integrity": "sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13", - "@babel/helper-replace-supers": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-replace-supers": "^7.12.1" } }, "@babel/plugin-transform-parameters": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.13.tgz", - "integrity": "sha512-e7QqwZalNiBRHCpJg/P8s/VJeSRYgmtWySs1JwvfwPqhBbiWfOcHDKdeAi6oAyIimoKWBlwc8oTgbZHdhCoVZA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", + "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-property-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz", - "integrity": "sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz", + "integrity": "sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-regenerator": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.13.tgz", - "integrity": "sha512-lxb2ZAvSLyJ2PEe47hoGWPmW22v7CtSl9jW8mingV4H2sEX/JOcrAj2nPuGWi56ERUm2bUpjKzONAuT6HCn2EA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", + "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", "dev": true, "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz", - "integrity": "sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", + "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-runtime": { - "version": "7.12.15", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.15.tgz", - "integrity": "sha512-OwptMSRnRWJo+tJ9v9wgAf72ydXWfYSXWhnQjZing8nGZSDFqU1MBleKM3+DriKkcbv7RagA8gVeB0A1PNlNow==", + "version": "7.12.17", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.17.tgz", + "integrity": "sha512-s+kIJxnaTj+E9Q3XxQZ5jOo+xcogSe3V78/iFQ5RmoT0jROdpcdxhfGdq/VLqW1hFSzw6VjqN8aQqTaAMixWsw==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.12.13", "@babel/helper-plugin-utils": "^7.12.13", "semver": "^5.5.1" + }, + "dependencies": { + "@babel/helper-module-imports": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz", + "integrity": "sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", + "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", + "dev": true + } } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz", - "integrity": "sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", + "integrity": "sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-spread": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.13.tgz", - "integrity": "sha512-dUCrqPIowjqk5pXsx1zPftSq4sT0aCeZVAxhdgs3AMgyaDmoUT0G+5h3Dzja27t76aUEIJWlFgPJqJ/d4dbTtg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz", + "integrity": "sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz", - "integrity": "sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz", + "integrity": "sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-template-literals": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.13.tgz", - "integrity": "sha512-arIKlWYUgmNsF28EyfmiQHJLJFlAJNYkuQO10jL46ggjBpeb2re1P9K9YGxNJB45BqTbaslVysXDYm/g3sN/Qg==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz", + "integrity": "sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz", - "integrity": "sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ==", + "version": "7.12.10", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.10.tgz", + "integrity": "sha512-JQ6H8Rnsogh//ijxspCjc21YPd3VLVoYtAwv3zQmqAt8YGYUtdo5usNhdl4b9/Vir2kPFZl6n1h0PfUz4hJhaA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-typescript": { @@ -1063,50 +1092,50 @@ } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz", - "integrity": "sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", + "integrity": "sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz", - "integrity": "sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA==", + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", + "integrity": "sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13" + "@babel/helper-create-regexp-features-plugin": "^7.12.1", + "@babel/helper-plugin-utils": "^7.10.4" } }, "@babel/preset-env": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.13.tgz", - "integrity": "sha512-JUVlizG8SoFTz4LmVUL8++aVwzwxcvey3N0j1tRbMAXVEy95uQ/cnEkmEKHN00Bwq4voAV3imQGnQvpkLAxsrw==", + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.11.tgz", + "integrity": "sha512-j8Tb+KKIXKYlDBQyIOy4BLxzv1NUOwlHfZ74rvW+Z0Gp4/cI2IMDPBWAgWceGcE7aep9oL/0K9mlzlMGxA8yNw==", "dev": true, "requires": { - "@babel/compat-data": "^7.12.13", - "@babel/helper-compilation-targets": "^7.12.13", - "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/compat-data": "^7.12.7", + "@babel/helper-compilation-targets": "^7.12.5", + "@babel/helper-module-imports": "^7.12.5", + "@babel/helper-plugin-utils": "^7.10.4", "@babel/helper-validator-option": "^7.12.11", - "@babel/plugin-proposal-async-generator-functions": "^7.12.13", - "@babel/plugin-proposal-class-properties": "^7.12.13", + "@babel/plugin-proposal-async-generator-functions": "^7.12.1", + "@babel/plugin-proposal-class-properties": "^7.12.1", "@babel/plugin-proposal-dynamic-import": "^7.12.1", - "@babel/plugin-proposal-export-namespace-from": "^7.12.13", - "@babel/plugin-proposal-json-strings": "^7.12.13", - "@babel/plugin-proposal-logical-assignment-operators": "^7.12.13", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.13", - "@babel/plugin-proposal-numeric-separator": "^7.12.13", - "@babel/plugin-proposal-object-rest-spread": "^7.12.13", - "@babel/plugin-proposal-optional-catch-binding": "^7.12.13", - "@babel/plugin-proposal-optional-chaining": "^7.12.13", - "@babel/plugin-proposal-private-methods": "^7.12.13", - "@babel/plugin-proposal-unicode-property-regex": "^7.12.13", + "@babel/plugin-proposal-export-namespace-from": "^7.12.1", + "@babel/plugin-proposal-json-strings": "^7.12.1", + "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", + "@babel/plugin-proposal-numeric-separator": "^7.12.7", + "@babel/plugin-proposal-object-rest-spread": "^7.12.1", + "@babel/plugin-proposal-optional-catch-binding": "^7.12.1", + "@babel/plugin-proposal-optional-chaining": "^7.12.7", + "@babel/plugin-proposal-private-methods": "^7.12.1", + "@babel/plugin-proposal-unicode-property-regex": "^7.12.1", "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-properties": "^7.12.1", "@babel/plugin-syntax-dynamic-import": "^7.8.0", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", "@babel/plugin-syntax-json-strings": "^7.8.0", @@ -1116,41 +1145,41 @@ "@babel/plugin-syntax-object-rest-spread": "^7.8.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.12.13", - "@babel/plugin-transform-arrow-functions": "^7.12.13", - "@babel/plugin-transform-async-to-generator": "^7.12.13", - "@babel/plugin-transform-block-scoped-functions": "^7.12.13", - "@babel/plugin-transform-block-scoping": "^7.12.13", - "@babel/plugin-transform-classes": "^7.12.13", - "@babel/plugin-transform-computed-properties": "^7.12.13", - "@babel/plugin-transform-destructuring": "^7.12.13", - "@babel/plugin-transform-dotall-regex": "^7.12.13", - "@babel/plugin-transform-duplicate-keys": "^7.12.13", - "@babel/plugin-transform-exponentiation-operator": "^7.12.13", - "@babel/plugin-transform-for-of": "^7.12.13", - "@babel/plugin-transform-function-name": "^7.12.13", - "@babel/plugin-transform-literals": "^7.12.13", - "@babel/plugin-transform-member-expression-literals": "^7.12.13", - "@babel/plugin-transform-modules-amd": "^7.12.13", - "@babel/plugin-transform-modules-commonjs": "^7.12.13", - "@babel/plugin-transform-modules-systemjs": "^7.12.13", - "@babel/plugin-transform-modules-umd": "^7.12.13", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.13", - "@babel/plugin-transform-new-target": "^7.12.13", - "@babel/plugin-transform-object-super": "^7.12.13", - "@babel/plugin-transform-parameters": "^7.12.13", - "@babel/plugin-transform-property-literals": "^7.12.13", - "@babel/plugin-transform-regenerator": "^7.12.13", - "@babel/plugin-transform-reserved-words": "^7.12.13", - "@babel/plugin-transform-shorthand-properties": "^7.12.13", - "@babel/plugin-transform-spread": "^7.12.13", - "@babel/plugin-transform-sticky-regex": "^7.12.13", - "@babel/plugin-transform-template-literals": "^7.12.13", - "@babel/plugin-transform-typeof-symbol": "^7.12.13", - "@babel/plugin-transform-unicode-escapes": "^7.12.13", - "@babel/plugin-transform-unicode-regex": "^7.12.13", + "@babel/plugin-syntax-top-level-await": "^7.12.1", + "@babel/plugin-transform-arrow-functions": "^7.12.1", + "@babel/plugin-transform-async-to-generator": "^7.12.1", + "@babel/plugin-transform-block-scoped-functions": "^7.12.1", + "@babel/plugin-transform-block-scoping": "^7.12.11", + "@babel/plugin-transform-classes": "^7.12.1", + "@babel/plugin-transform-computed-properties": "^7.12.1", + "@babel/plugin-transform-destructuring": "^7.12.1", + "@babel/plugin-transform-dotall-regex": "^7.12.1", + "@babel/plugin-transform-duplicate-keys": "^7.12.1", + "@babel/plugin-transform-exponentiation-operator": "^7.12.1", + "@babel/plugin-transform-for-of": "^7.12.1", + "@babel/plugin-transform-function-name": "^7.12.1", + "@babel/plugin-transform-literals": "^7.12.1", + "@babel/plugin-transform-member-expression-literals": "^7.12.1", + "@babel/plugin-transform-modules-amd": "^7.12.1", + "@babel/plugin-transform-modules-commonjs": "^7.12.1", + "@babel/plugin-transform-modules-systemjs": "^7.12.1", + "@babel/plugin-transform-modules-umd": "^7.12.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.1", + "@babel/plugin-transform-new-target": "^7.12.1", + "@babel/plugin-transform-object-super": "^7.12.1", + "@babel/plugin-transform-parameters": "^7.12.1", + "@babel/plugin-transform-property-literals": "^7.12.1", + "@babel/plugin-transform-regenerator": "^7.12.1", + "@babel/plugin-transform-reserved-words": "^7.12.1", + "@babel/plugin-transform-shorthand-properties": "^7.12.1", + "@babel/plugin-transform-spread": "^7.12.1", + "@babel/plugin-transform-sticky-regex": "^7.12.7", + "@babel/plugin-transform-template-literals": "^7.12.1", + "@babel/plugin-transform-typeof-symbol": "^7.12.10", + "@babel/plugin-transform-unicode-escapes": "^7.12.1", + "@babel/plugin-transform-unicode-regex": "^7.12.1", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.12.13", + "@babel/types": "^7.12.11", "core-js-compat": "^3.8.0", "semver": "^5.5.0" } @@ -1188,45 +1217,45 @@ } }, "@babel/runtime": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.13.tgz", - "integrity": "sha512-8+3UMPBrjFa/6TtKi/7sehPKqfAm4g6K+YQjyyFOLUTxzOngcRZTlAVY8sc2CORJYqdHQY8gRPHmn+qo15rCBw==", + "version": "7.12.18", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.18.tgz", + "integrity": "sha512-BogPQ7ciE6SYAUPtlm9tWbgI9+2AgqSam6QivMgXgAT+fKbgppaj4ZX15MHeLC1PVF5sNk70huBu20XxWOs8Cg==", "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/runtime-corejs3": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.13.tgz", - "integrity": "sha512-8fSpqYRETHATtNitsCXq8QQbKJP31/KnDl2Wz2Vtui9nKzjss2ysuZtyVsWjBtvkeEFo346gkwjYPab1hvrXkQ==", + "version": "7.12.18", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.18.tgz", + "integrity": "sha512-ngR7yhNTjDxxe1VYmhqQqqXZWujGb6g0IoA4qeG6MxNGRnIw2Zo8ImY8HfaQ7l3T6GklWhdNfyhWk0C0iocdVA==", "requires": { "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.4" } }, "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "version": "7.12.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", + "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.12.7", + "@babel/types": "^7.12.7" } }, "@babel/traverse": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz", - "integrity": "sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==", + "version": "7.12.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", + "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.12.13", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13", + "@babel/code-frame": "^7.12.11", + "@babel/generator": "^7.12.11", + "@babel/helper-function-name": "^7.12.11", + "@babel/helper-split-export-declaration": "^7.12.11", + "@babel/parser": "^7.12.11", + "@babel/types": "^7.12.12", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" @@ -1317,9 +1346,9 @@ } }, "@ethersproject/abstract-provider": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.0.9.tgz", - "integrity": "sha512-X9fMkqpeu9ayC3JyBkeeZhn35P4xQkpGX/l+FrxDtEW9tybf/UWXSMi8bGThpPtfJ6q6U2LDetXSpSwK4TfYQQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.0.8.tgz", + "integrity": "sha512-fqJXkewcGdi8LogKMgRyzc/Ls2js07yor7+g9KfPs09uPOcQLg7cc34JN+lk34HH9gg2HU0DIA5797ZR8znkfw==", "requires": { "@ethersproject/bignumber": "^5.0.13", "@ethersproject/bytes": "^5.0.9", @@ -1331,9 +1360,9 @@ } }, "@ethersproject/abstract-signer": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.0.12.tgz", - "integrity": "sha512-qt4jAEzQGPZ31My1gFGPzzJHJveYhVycW7RHkuX0W8fvMdg7wr0uvP7mQEptMVrb+jYwsVktCf6gBGwWDpFiTA==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.0.11.tgz", + "integrity": "sha512-RKOgPSEYafknA62SrD3OCK42AllHE4YBfKYXyQeM+sBP7Nq3X5FpzeoY4uzC43P4wIhmNoTHCKQuwnX7fBqb6Q==", "requires": { "@ethersproject/abstract-provider": "^5.0.8", "@ethersproject/bignumber": "^5.0.13", @@ -1343,9 +1372,9 @@ } }, "@ethersproject/address": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.0.10.tgz", - "integrity": "sha512-70vqESmW5Srua1kMDIN6uVfdneZMaMyRYH4qPvkAXGkbicrCOsA9m01vIloA4wYiiF+HLEfL1ENKdn5jb9xiAw==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.0.9.tgz", + "integrity": "sha512-gKkmbZDMyGbVjr8nA5P0md1GgESqSGH7ILIrDidPdNXBl4adqbuA3OAuZx/O2oGpL6PtJ9BDa0kHheZ1ToHU3w==", "requires": { "@ethersproject/bignumber": "^5.0.13", "@ethersproject/bytes": "^5.0.9", @@ -1355,26 +1384,26 @@ } }, "@ethersproject/base64": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.0.8.tgz", - "integrity": "sha512-PNbpHOMgZpZ1skvQl119pV2YkCPXmZTxw+T92qX0z7zaMFPypXWTZBzim+hUceb//zx4DFjeGT4aSjZRTOYThg==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.0.7.tgz", + "integrity": "sha512-S5oh5DVfCo06xwJXT8fQC68mvJfgScTl2AXvbYMsHNfIBTDb084Wx4iA9MNlEReOv6HulkS+gyrUM/j3514rSw==", "requires": { "@ethersproject/bytes": "^5.0.9" } }, "@ethersproject/basex": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.0.8.tgz", - "integrity": "sha512-PCVKZIShBQUqAXjJSvaCidThPvL0jaaQZcewJc0sf8Xx05BizaOS8r3jdPdpNdY+/qZtRDqwHTSKjvR/xssyLQ==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.0.7.tgz", + "integrity": "sha512-OsXnRsujGmYD9LYyJlX+cVe5KfwgLUbUJrJMWdzRWogrygXd5HvGd7ygX1AYjlu1z8W/+t2FoQnczDR/H2iBjA==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/properties": "^5.0.7" } }, "@ethersproject/bignumber": { - "version": "5.0.14", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.14.tgz", - "integrity": "sha512-Q4TjMq9Gg3Xzj0aeJWqJgI3tdEiPiET7Y5OtNtjTAODZ2kp4y9jMNg97zVcvPedFvGROdpGDyCI77JDFodUzOw==", + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.13.tgz", + "integrity": "sha512-b89bX5li6aK492yuPP5mPgRVgIxxBP7ksaBtKX5QQBsrZTpNOjf/MR4CjcUrAw8g+RQuD6kap9lPjFgY4U1/5A==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8", @@ -1382,25 +1411,25 @@ } }, "@ethersproject/bytes": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.10.tgz", - "integrity": "sha512-vpu0v1LZ1j1s9kERQIMnVU69MyHEzUff7nqK9XuCU4vx+AM8n9lU2gj7jtJIvGSt9HzatK/6I6bWusI5nyuaTA==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.9.tgz", + "integrity": "sha512-k+17ZViDtAugC0s7HM6rdsTWEdIYII4RPCDkPEuxKc6i40Bs+m6tjRAtCECX06wKZnrEoR9pjOJRXHJ/VLoOcA==", "requires": { "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/constants": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.9.tgz", - "integrity": "sha512-2uAKH89UcaJP/Sc+54u92BtJtZ4cPgcS1p0YbB1L3tlkavwNvth+kNCUplIB1Becqs7BOZr0B/3dMNjhJDy4Dg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.8.tgz", + "integrity": "sha512-sCc73pFBsl59eDfoQR5OCEZCRv5b0iywadunti6MQIr5lt3XpwxK1Iuzd8XSFO02N9jUifvuZRrt0cY0+NBgTg==", "requires": { "@ethersproject/bignumber": "^5.0.13" } }, "@ethersproject/contracts": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.0.10.tgz", - "integrity": "sha512-h9kdvllwT6B1LyUXeNQIb7Y6u6ZprP5LUiQIjSqvOehhm1sFZcaVtydsSa0LIg3SBC5QF0M7zH5p7EtI2VD0rQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.0.9.tgz", + "integrity": "sha512-CCTxVeDh6sjdSEbjzONhtwPjECvaHE62oGkY8M7kP0CHmgLD2SEGel0HZib8e5oQKRKGly9AKcUFW4g3rQ0AQw==", "requires": { "@ethersproject/abi": "^5.0.10", "@ethersproject/abstract-provider": "^5.0.8", @@ -1414,9 +1443,9 @@ } }, "@ethersproject/hash": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.0.11.tgz", - "integrity": "sha512-H3KJ9fk33XWJ2djAW03IL7fg3DsDMYjO1XijiUb1hJ85vYfhvxu0OmsU7d3tg2Uv1H1kFSo8ghr3WFQ8c+NL3g==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.0.10.tgz", + "integrity": "sha512-Tf0bvs6YFhw28LuHnhlDWyr0xfcDxSXdwM4TcskeBbmXVSKLv3bJQEEEBFUcRX0fJuslR3gCVySEaSh7vuMx5w==", "requires": { "@ethersproject/abstract-signer": "^5.0.10", "@ethersproject/address": "^5.0.9", @@ -1429,9 +1458,9 @@ } }, "@ethersproject/hdnode": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.0.9.tgz", - "integrity": "sha512-S5UMmIC6XfFtqhUK4uTjD8GPNzSbE+sZ/0VMqFnA3zAJ+cEFZuEyhZDYnl2ItGJzjT4jsy+uEy1SIl3baYK1PQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.0.8.tgz", + "integrity": "sha512-Mscpjd7BBjxYSWghaNMwV0xrBBkOoCq6YEPRm9MgE24CiBlzzbfEB5DGq6hiZqhQaxPkdCUtKKqZi3nt9hx43g==", "requires": { "@ethersproject/abstract-signer": "^5.0.10", "@ethersproject/basex": "^5.0.7", @@ -1448,9 +1477,9 @@ } }, "@ethersproject/json-wallets": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.0.11.tgz", - "integrity": "sha512-0GhWScWUlXXb4qJNp0wmkU95QS3YdN9UMOfMSEl76CRANWWrmyzxcBVSXSBu5iQ0/W8wO+xGlJJ3tpA6v3mbIw==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.0.10.tgz", + "integrity": "sha512-Ux36u+d7Dm0M5AQ+mWuHdvfGPMN8K1aaLQgwzrsD4ELTWlwRuHuQbmn7/GqeOpbfaV6POLwdYcBk2TXjlGp/IQ==", "requires": { "@ethersproject/abstract-signer": "^5.0.10", "@ethersproject/address": "^5.0.9", @@ -1468,48 +1497,48 @@ } }, "@ethersproject/keccak256": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.0.8.tgz", - "integrity": "sha512-zoGbwXcWWs9MX4NOAZ7N0hhgIRl4Q/IO/u9c/RHRY4WqDy3Ywm0OLamEV53QDwhjwn3YiiVwU1Ve5j7yJ0a/KQ==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.0.7.tgz", + "integrity": "sha512-zpUBmofWvx9PGfc7IICobgFQSgNmTOGTGLUxSYqZzY/T+b4y/2o5eqf/GGmD7qnTGzKQ42YlLNo+LeDP2qe55g==", "requires": { "@ethersproject/bytes": "^5.0.9", "js-sha3": "0.5.7" } }, "@ethersproject/logger": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.9.tgz", - "integrity": "sha512-kV3Uamv3XOH99Xf3kpIG3ZkS7mBNYcLDM00JSDtNgNB4BihuyxpQzIZPRIDmRi+95Z/R1Bb0X2kUNHa/kJoVrw==" + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", + "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" }, "@ethersproject/networks": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.0.8.tgz", - "integrity": "sha512-PYpptlO2Tu5f/JEBI5hdlMds5k1DY1QwVbh3LKPb3un9dQA2bC51vd2/gRWAgSBpF3kkmZOj4FhD7ATLX4H+DA==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.0.7.tgz", + "integrity": "sha512-dI14QATndIcUgcCBL1c5vUr/YsI5cCHLN81rF7PU+yS7Xgp2/Rzbr9+YqpC6NBXHFUASjh6GpKqsVMpufAL0BQ==", "requires": { "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/pbkdf2": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.0.8.tgz", - "integrity": "sha512-UlmAMGbIPaS2xXsI38FbePVTfJMuU9jnwcqVn3p88HxPF4kD897ha+l3TNsBqJqf32UbQL5GImnf1oJkSKq4vQ==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.0.7.tgz", + "integrity": "sha512-0SNLNixPMqnosH6pyc4yPiUu/C9/Jbu+f6I8GJW9U2qNpMBddmRJviwseoha5Zw1V+Aw0Z/yvYyzIIE8yPXqLA==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/sha2": "^5.0.7" } }, "@ethersproject/properties": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.8.tgz", - "integrity": "sha512-zEnLMze2Eu2VDPj/05QwCwMKHh506gpT9PP9KPVd4dDB+5d6AcROUYVLoIIQgBYK7X/Gw0UJmG3oVtnxOQafAw==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.7.tgz", + "integrity": "sha512-812H1Rus2vjw0zbasfDI1GLNPDsoyX1pYqiCgaR1BuyKxUTbwcH1B+214l6VGe1v+F6iEVb7WjIwMjKhb4EUsg==", "requires": { "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/providers": { - "version": "5.0.22", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.0.22.tgz", - "integrity": "sha512-6C6agQsz/7FRFfZFok+m1HVUS6G+IU1grLwBx9+W11SF3UeP8vquXRMVDfOiKWyvy+g4bJT1+9C/QuC8lG08DQ==", + "version": "5.0.19", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.0.19.tgz", + "integrity": "sha512-G+flo1jK1y/rvQy6b71+Nu7qOlkOKz+XqpgqFMZslkCzGuzQRmk9Qp7Ln4soK8RSyP1e5TCujaRf1H+EZahoaw==", "requires": { "@ethersproject/abstract-provider": "^5.0.8", "@ethersproject/abstract-signer": "^5.0.10", @@ -1540,27 +1569,27 @@ } }, "@ethersproject/random": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.0.8.tgz", - "integrity": "sha512-4rHtotmd9NjklW0eDvByicEkL+qareIyFSbG1ShC8tPJJSAC0g55oQWzw+3nfdRCgBHRuEE7S8EcPcTVPvZ9cA==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.0.7.tgz", + "integrity": "sha512-PxSRWwN3s+FH9AWMZU6AcWJsNQ9KzqKV6NgdeKPtxahdDjCuXxTAuzTZNXNRK+qj+Il351UnweAGd+VuZcOAlQ==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/rlp": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.0.8.tgz", - "integrity": "sha512-E4wdFs8xRNJfzNHmnkC8w5fPeT4Wd1U2cust3YeT16/46iSkLT8nn8ilidC6KhR7hfuSZE4UqSPzyk76p7cdZg==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.0.7.tgz", + "integrity": "sha512-ulUTVEuV7PT4jJTPpfhRHK57tkLEDEY9XSYJtrSNHOqdwMvH0z7BM2AKIMq4LVDlnu4YZASdKrkFGEIO712V9w==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/sha2": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.0.8.tgz", - "integrity": "sha512-ILP1ZgyvDj4rrdE+AXrTv9V88m7x87uga2VZ/FeULKPumOEw/4bGnJz/oQ8zDnDvVYRCJ+48VaQBS2CFLbk1ww==", + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.0.7.tgz", + "integrity": "sha512-MbUqz68hhp5RsaZdqi1eg1rrtiqt5wmhRYqdA7MX8swBkzW2KiLgK+Oh25UcWhUhdi1ImU9qrV6if5j0cC7Bxg==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8", @@ -1579,20 +1608,20 @@ } }, "@ethersproject/signing-key": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.0.10.tgz", - "integrity": "sha512-w5it3GbFOvN6e0mTd5gDNj+bwSe6L9jqqYjU+uaYS8/hAEp4qYLk5p8ZjbJJkNn7u1p0iwocp8X9oH/OdK8apA==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.0.8.tgz", + "integrity": "sha512-YKxQM45eDa6WAD+s3QZPdm1uW1MutzVuyoepdRRVmMJ8qkk7iOiIhUkZwqKLNxKzEJijt/82ycuOREc9WBNAKg==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8", "@ethersproject/properties": "^5.0.7", - "elliptic": "6.5.4" + "elliptic": "6.5.3" } }, "@ethersproject/solidity": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.0.9.tgz", - "integrity": "sha512-LIxSAYEQgLRXE3mRPCq39ou61kqP8fDrGqEeNcaNJS3aLbmAOS8MZp56uK++WsdI9hj8sNsFh78hrAa6zR9Jag==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.0.8.tgz", + "integrity": "sha512-OJkyBq9KaoGsi8E8mYn6LX+vKyCURvxSp0yuGBcOqEFM3vkn9PsCiXsHdOXdNBvlHG5evJXwAYC2UR0TzgJeKA==", "requires": { "@ethersproject/bignumber": "^5.0.13", "@ethersproject/bytes": "^5.0.9", @@ -1612,9 +1641,9 @@ } }, "@ethersproject/transactions": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.0.10.tgz", - "integrity": "sha512-Tqpp+vKYQyQdJQQk4M73tDzO7ODf2D42/sJOcKlDAAbdSni13v6a+31hUdo02qYXhVYwIs+ZjHnO4zKv5BNk8w==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.0.9.tgz", + "integrity": "sha512-0Fu1yhdFBkrbMjenEr+39tmDxuHmaw0pe9Jb18XuKoItj7Z3p7+UzdHLr2S/okvHDHYPbZE5gtANDdQ3ZL1nBA==", "requires": { "@ethersproject/address": "^5.0.9", "@ethersproject/bignumber": "^5.0.13", @@ -1628,9 +1657,9 @@ } }, "@ethersproject/units": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.0.10.tgz", - "integrity": "sha512-eaiHi9ham5lbC7qpqxpae7OY/nHJUnRUnFFuEwi2VB5Nwe3Np468OAV+e+HR+jAK4fHXQE6PFBTxWGtnZuO37g==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.0.9.tgz", + "integrity": "sha512-4jIkcMVrJ3lCgXMO4M/2ww0/T/IN08vJTZld7FIAwa6aoBDTAy71+sby3sShl1SG3HEeKYbI3fBWauCUgPRUpQ==", "requires": { "@ethersproject/bignumber": "^5.0.13", "@ethersproject/constants": "^5.0.8", @@ -1638,9 +1667,9 @@ } }, "@ethersproject/wallet": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.0.11.tgz", - "integrity": "sha512-2Fg/DOvUltR7aZTOyWWlQhru+SKvq2UE3uEhXSyCFgMqDQNuc2nHXh1SHJtN65jsEbjVIppOe1Q7EQMvhmeeRw==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.0.10.tgz", + "integrity": "sha512-5siYr38NhqZKH6DUr6u4PdhgOKur8Q6sw+JID2TitEUmW0tOl8f6rpxAe77tw6SJT60D2UcvgsyLtl32+Nl+ig==", "requires": { "@ethersproject/abstract-provider": "^5.0.8", "@ethersproject/abstract-signer": "^5.0.10", @@ -1660,9 +1689,9 @@ } }, "@ethersproject/web": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.0.13.tgz", - "integrity": "sha512-G3x/Ns7pQm21ALnWLbdBI5XkW/jrsbXXffI9hKNPHqf59mTxHYtlNiSwxdoTSwCef3Hn7uvGZpaSgTyxs7IufQ==", + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.0.12.tgz", + "integrity": "sha512-gVxS5iW0bgidZ76kr7LsTxj4uzN5XpCLzvZrLp8TP+4YgxHfCeetFyQkRPgBEAJdNrexdSBayvyJvzGvOq0O8g==", "requires": { "@ethersproject/base64": "^5.0.7", "@ethersproject/bytes": "^5.0.9", @@ -1672,9 +1701,9 @@ } }, "@ethersproject/wordlists": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.0.9.tgz", - "integrity": "sha512-Sn6MTjZkfbriod6GG6+p43W09HOXT4gwcDVNj0YoPYlo4Zq2Fk6b1CU9KUX3c6aI17PrgYb4qwZm5BMuORyqyQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.0.8.tgz", + "integrity": "sha512-px2mloc1wAcdTbzv0ZotTx+Uh/dfnDO22D9Rx8xr7+/PUwAhZQjoJ9t7Hn72nsaN83rWBXsLvFcIRZju4GIaEQ==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/hash": "^5.0.10", @@ -2349,13 +2378,24 @@ } }, "@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.0.tgz", + "integrity": "sha512-Iv2iq0JuyYjKeFkSR4LPaCdDZwlGK9X2cP/01nJcp3yMJ1FjNd9vpiEYvLUgzBxKPg2SFmaOhizoQsPc0LWeOQ==", "dev": true, "requires": { "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" + "rimraf": "^2.7.1" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } } }, "@peculiar/asn1-schema": { @@ -2509,10 +2549,20 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "26.0.20", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.20.tgz", + "integrity": "sha512-9zi2Y+5USJRxd0FsahERhBwlcvFh6D2GLQnY2FH2BzK8J9s9omvNHIbvABwIluXa0fD8XVKMLTO0aOEuUfACAA==", + "dev": true, + "requires": { + "jest-diff": "^26.0.0", + "pretty-format": "^26.0.0" + } + }, "@types/json-schema": { - "version": "7.0.7", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", - "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", + "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", "dev": true }, "@types/json5": { @@ -2522,9 +2572,9 @@ "dev": true }, "@types/node": { - "version": "14.14.25", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.25.tgz", - "integrity": "sha512-EPpXLOVqDvisVxtlbvzfyqSsFeQxltFbluZNRndIb8tr9KiBnYNLzrc1N3pyKUCww2RNrfHDViqDWWE1LCJQtQ==", + "version": "14.14.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.21.tgz", + "integrity": "sha512-cHYfKsnwllYhjOzuC5q1VpguABBeecUp24yFluHpn/BQaVxB1CuQ1FSRZCzrPxrkIfWISXV2LbeoBthLWg0+0A==", "dev": true }, "@types/normalize-package-data": { @@ -2534,9 +2584,9 @@ "dev": true }, "@types/prettier": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.0.tgz", - "integrity": "sha512-O3SQC6+6AySHwrspYn2UvC6tjo6jCTMMmylxZUFhE1CulVu5l3AxU6ca9lrJDTQDVllF62LIxVSx5fuYL6LiZg==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.6.tgz", + "integrity": "sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA==", "dev": true }, "@types/qs": { @@ -2552,9 +2602,9 @@ "dev": true }, "@types/yargs": { - "version": "15.0.13", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", - "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", + "version": "15.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz", + "integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -2857,24 +2907,24 @@ } }, "@webpack-cli/configtest": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.1.tgz", - "integrity": "sha512-B+4uBUYhpzDXmwuo3V9yBH6cISwxEI4J+NO5ggDaGEEHb0osY/R7MzeKc0bHURXQuZjMM4qD+bSJCKIuI3eNBQ==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.0.tgz", + "integrity": "sha512-Un0SdBoN1h4ACnIO7EiCjWuyhNI0Jl96JC+63q6xi4HDUYRZn8Auluea9D+v9NWKc5J4sICVEltdBaVjLX39xw==", "dev": true }, "@webpack-cli/info": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.2.tgz", - "integrity": "sha512-5U9kUJHnwU+FhKH4PWGZuBC1hTEPYyxGSL5jjoBI96Gx8qcYJGOikpiIpFoTq8mmgX3im2zAo2wanv/alD74KQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.1.tgz", + "integrity": "sha512-fLnDML5HZ5AEKzHul8xLAksoKN2cibu6MgonkUj8R9V7bbeVRkd1XbGEGWrAUNYHbX1jcqCsDEpBviE5StPMzQ==", "dev": true, "requires": { "envinfo": "^7.7.3" } }, "@webpack-cli/serve": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.3.0.tgz", - "integrity": "sha512-k2p2VrONcYVX1wRRrf0f3X2VGltLWcv+JzXRBDmvCxGlCeESx4OXw91TsWeKOkp784uNoVQo313vxJFHXPPwfw==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.2.tgz", + "integrity": "sha512-03GkWxcgFfm8+WIwcsqJb9agrSDNDDoxaNnexPnCCexP5SCE4IgFd9lNpSy+K2nFqVMpgTFw6SwbmVAVTndVew==", "dev": true }, "@xtuc/ieee754": { @@ -3148,6 +3198,13 @@ "integrity": "sha512-yG89F0j9B4B0MKIcFyWWxnpZPLaNTjCj4tkE3fjbAoo0qmpGw0PYYqSbX/4ebnd9Icn8ZgK4K1fvDyEtW1JYtQ==", "requires": { "pvutils": "^1.0.17" + }, + "dependencies": { + "pvutils": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.0.17.tgz", + "integrity": "sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ==" + } } }, "assert": { @@ -3939,16 +3996,16 @@ } }, "browserslist": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", - "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz", + "integrity": "sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001181", + "caniuse-lite": "^1.0.30001173", "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.649", + "electron-to-chromium": "^1.3.634", "escalade": "^3.1.1", - "node-releases": "^1.1.70" + "node-releases": "^1.1.69" } }, "bser": { @@ -4031,6 +4088,17 @@ "ssri": "^8.0.0", "tar": "^6.0.2", "unique-filename": "^1.1.1" + }, + "dependencies": { + "p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "dev": true, + "requires": { + "aggregate-error": "^3.0.0" + } + } } }, "cache-base": { @@ -4073,9 +4141,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001185", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001185.tgz", - "integrity": "sha512-Fpi4kVNtNvJ15H0F6vwmXtb3tukv3Zg3qhKkOGUq7KJ1J6b9kf4dnNgtEAFXhRsJo0gNj9W60+wBvn0JcTvdTg==", + "version": "1.0.30001178", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001178.tgz", + "integrity": "sha512-VtdZLC0vsXykKni8Uztx45xynytOi71Ufx9T8kHptSw9AL4dpqailUJJHavttuzUe1KYuBYtChiWv+BAb7mPmQ==", "dev": true }, "capture-exit": { @@ -4135,9 +4203,9 @@ "dev": true }, "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.0.tgz", + "integrity": "sha512-JgQM9JS92ZbFR4P90EvmzNpSGhpPBGBSj10PILeDyYFwp4h2/D9OM03wsJ4zW1fEp4ka2DGrnUeD7FuvQ2aZ2Q==", "dev": true, "optional": true, "requires": { @@ -4631,12 +4699,12 @@ "dev": true }, "core-js-compat": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.3.tgz", - "integrity": "sha512-1sCb0wBXnBIL16pfFG1Gkvei6UzvKyTNYpiC41yrdjEv0UoJoq9E/abTMzyYJ6JpTkAj15dLjbqifIzEBDVvog==", + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.2.tgz", + "integrity": "sha512-LO8uL9lOIyRRrQmZxHZFl1RV+ZbcsAkFWTktn5SmH40WgLtSNYN4m4W2v9ONT147PxBY/XrRhrWq8TlvObyUjQ==", "dev": true, "requires": { - "browserslist": "^4.16.1", + "browserslist": "^4.16.0", "semver": "7.0.0" }, "dependencies": { @@ -4649,9 +4717,9 @@ } }, "core-js-pure": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.8.3.tgz", - "integrity": "sha512-V5qQZVAr9K0xu7jXg1M7qTEwuxUgqr7dUOezGaNa7i+Xn9oXAU/d1fzqD9ObuwpVQOaorO5s70ckyi1woP9lVA==" + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.9.0.tgz", + "integrity": "sha512-3pEcmMZC9Cq0D4ZBh3pe2HLtqxpGNJBLXF/kZ2YzK17RbKp94w0HFbdbSx8H8kAlZG5k76hvLrkPm57Uyef+kg==" }, "core-util-is": { "version": "1.0.2", @@ -5171,23 +5239,23 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.663", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.663.tgz", - "integrity": "sha512-xkVkzHj6k3oRRGlmdgUCCLSLhtFYHDCTH7SeK+LJdJjnsLcrdbpr8EYmfMQhez3V/KPO5UScSpzQ0feYX6Qoyw==", + "version": "1.3.641", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.641.tgz", + "integrity": "sha512-b0DLhsHSHESC1I+Nx6n4w4Lr61chMd3m/av1rZQhS2IXTzaS5BMM5N+ldWdMIlni9CITMRM09m8He4+YV/92TA==", "dev": true }, "elliptic": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", - "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", "requires": { - "bn.js": "^4.11.9", - "brorand": "^1.1.0", + "bn.js": "^4.4.0", + "brorand": "^1.0.1", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.1", - "inherits": "^2.0.4", - "minimalistic-assert": "^1.0.1", - "minimalistic-crypto-utils": "^1.0.1" + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" } }, "emittery": { @@ -5286,9 +5354,9 @@ } }, "envinfo": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.4.tgz", - "integrity": "sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.3.tgz", + "integrity": "sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA==", "dev": true }, "errno": { @@ -5481,15 +5549,6 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { - "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "requires": { - "@babel/highlight": "^7.10.4" - } - }, "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", @@ -5804,9 +5863,9 @@ } }, "eslint-plugin-promise": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz", - "integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", + "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", "dev": true }, "eslint-scope": { @@ -5920,40 +5979,68 @@ "dev": true }, "ethers": { - "version": "5.0.30", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.0.30.tgz", - "integrity": "sha512-CdY/zb8d0uEBaWNmDkAVWXO8FLs2plAPOjgukgYC95L5VKIZzZaCav7PAeG2IqGico4vtNu8l3ibXdXd6FqjrQ==", - "requires": { - "@ethersproject/abi": "5.0.12", - "@ethersproject/abstract-provider": "5.0.9", - "@ethersproject/abstract-signer": "5.0.12", - "@ethersproject/address": "5.0.10", - "@ethersproject/base64": "5.0.8", - "@ethersproject/basex": "5.0.8", - "@ethersproject/bignumber": "5.0.14", - "@ethersproject/bytes": "5.0.10", - "@ethersproject/constants": "5.0.9", - "@ethersproject/contracts": "5.0.10", - "@ethersproject/hash": "5.0.11", - "@ethersproject/hdnode": "5.0.9", - "@ethersproject/json-wallets": "5.0.11", - "@ethersproject/keccak256": "5.0.8", - "@ethersproject/logger": "5.0.9", - "@ethersproject/networks": "5.0.8", - "@ethersproject/pbkdf2": "5.0.8", - "@ethersproject/properties": "5.0.8", - "@ethersproject/providers": "5.0.22", - "@ethersproject/random": "5.0.8", - "@ethersproject/rlp": "5.0.8", - "@ethersproject/sha2": "5.0.8", - "@ethersproject/signing-key": "5.0.10", - "@ethersproject/solidity": "5.0.9", - "@ethersproject/strings": "5.0.9", - "@ethersproject/transactions": "5.0.10", - "@ethersproject/units": "5.0.10", - "@ethersproject/wallet": "5.0.11", - "@ethersproject/web": "5.0.13", - "@ethersproject/wordlists": "5.0.9" + "version": "5.0.26", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.0.26.tgz", + "integrity": "sha512-MqA8Fvutn3qEW0yBJOHeV6KZmRpF2rqlL2B5058AGkUFsuu6j5Ns/FRlMsbGeQwBz801IB23jQp7vjRfFsKSkg==", + "requires": { + "@ethersproject/abi": "5.0.10", + "@ethersproject/abstract-provider": "5.0.8", + "@ethersproject/abstract-signer": "5.0.11", + "@ethersproject/address": "5.0.9", + "@ethersproject/base64": "5.0.7", + "@ethersproject/basex": "5.0.7", + "@ethersproject/bignumber": "5.0.13", + "@ethersproject/bytes": "5.0.9", + "@ethersproject/constants": "5.0.8", + "@ethersproject/contracts": "5.0.9", + "@ethersproject/hash": "5.0.10", + "@ethersproject/hdnode": "5.0.8", + "@ethersproject/json-wallets": "5.0.10", + "@ethersproject/keccak256": "5.0.7", + "@ethersproject/logger": "5.0.8", + "@ethersproject/networks": "5.0.7", + "@ethersproject/pbkdf2": "5.0.7", + "@ethersproject/properties": "5.0.7", + "@ethersproject/providers": "5.0.19", + "@ethersproject/random": "5.0.7", + "@ethersproject/rlp": "5.0.7", + "@ethersproject/sha2": "5.0.7", + "@ethersproject/signing-key": "5.0.8", + "@ethersproject/solidity": "5.0.8", + "@ethersproject/strings": "5.0.8", + "@ethersproject/transactions": "5.0.9", + "@ethersproject/units": "5.0.9", + "@ethersproject/wallet": "5.0.10", + "@ethersproject/web": "5.0.12", + "@ethersproject/wordlists": "5.0.8" + }, + "dependencies": { + "@ethersproject/abi": { + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.0.10.tgz", + "integrity": "sha512-cfC3lGgotfxX3SMri4+CisOPwignoj/QGHW9J29spC4R4Qqcnk/SYuVkPFBMdLbvBp3f/pGiVqPNwont0TSXhg==", + "requires": { + "@ethersproject/address": "^5.0.9", + "@ethersproject/bignumber": "^5.0.13", + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/constants": "^5.0.8", + "@ethersproject/hash": "^5.0.10", + "@ethersproject/keccak256": "^5.0.7", + "@ethersproject/logger": "^5.0.8", + "@ethersproject/properties": "^5.0.7", + "@ethersproject/strings": "^5.0.8" + } + }, + "@ethersproject/strings": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.8.tgz", + "integrity": "sha512-5IsdXf8tMY8QuHl8vTLnk9ehXDDm6x9FB9S9Og5IA1GYhLe5ZewydXSjlJlsqU2t9HRbfv97OJZV/pX8DVA/Hw==", + "requires": { + "@ethersproject/bytes": "^5.0.9", + "@ethersproject/constants": "^5.0.8", + "@ethersproject/logger": "^5.0.8" + } + } } }, "eventemitter3": { @@ -6802,9 +6889,9 @@ "dev": true }, "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", + "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", "dev": true, "optional": true }, @@ -6882,9 +6969,9 @@ "dev": true }, "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz", + "integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==", "dev": true, "requires": { "function-bind": "^1.1.1", @@ -7111,9 +7198,9 @@ } }, "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", "dev": true }, "growl": { @@ -7523,9 +7610,9 @@ "dev": true }, "is-callable": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", - "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", "dev": true }, "is-ci": { @@ -7703,12 +7790,11 @@ "dev": true }, "is-regex": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", - "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", "dev": true, "requires": { - "call-bind": "^1.0.2", "has-symbols": "^1.0.1" } }, @@ -9540,9 +9626,9 @@ "dev": true }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", + "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", "dev": true, "requires": { "minimist": "^1.2.5" @@ -10731,9 +10817,9 @@ } }, "node-releases": { - "version": "1.1.70", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.70.tgz", - "integrity": "sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==", + "version": "1.1.69", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.69.tgz", + "integrity": "sha512-DGIjo79VDEyAnRlfSqYTsy+yoHd2IOjJiKUozD2MV2D85Vso6Bug56mb9tT/fY5Urt0iqk01H7x+llAruDR2zA==", "dev": true }, "node-status-codes": { @@ -11750,18 +11836,13 @@ "dev": true }, "pvtsutils": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.1.2.tgz", - "integrity": "sha512-Yfm9Dsk1zfEpOWCaJaHfqtNXAFWNNHMFSCLN6jTnhuCCBCC2nqge4sAgo7UrkRBoAAYIL8TN/6LlLoNfZD/b5A==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.1.1.tgz", + "integrity": "sha512-Evbhe6L4Sxwu4SPLQ4LQZhgfWDQO3qa1lju9jM5cxsQp8vE10VipcSmo7hiJW48TmiHgVLgDtC2TL6/+ND+IVg==", "requires": { - "tslib": "^2.1.0" + "tslib": "^2.0.3" } }, - "pvutils": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.0.17.tgz", - "integrity": "sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ==" - }, "qs": { "version": "6.9.6", "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", @@ -12096,9 +12177,9 @@ "dev": true }, "regjsparser": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.7.tgz", - "integrity": "sha512-ib77G0uxsA2ovgiYbCVGx4Pv3PSttAx2vIwidqQzbL2U5S4Q+j00HdSAneSBuyVcMvEnTXMjiGgB+DlXozVhpQ==", + "version": "0.6.6", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.6.tgz", + "integrity": "sha512-jjyuCp+IEMIm3N1H1LLTJW1EISEJV9+5oHdEyrt43Pg9cDSb6rrLZei2cVWpl0xTjmmlpec/lEQGYgM7xfpGCQ==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -12257,12 +12338,12 @@ "dev": true }, "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", "dev": true, "requires": { - "is-core-module": "^2.2.0", + "is-core-module": "^2.1.0", "path-parse": "^1.0.6" } }, @@ -12639,9 +12720,9 @@ } }, "sirv": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.11.tgz", - "integrity": "sha512-SR36i3/LSWja7AJNRBz4fF/Xjpn7lQFI30tZ434dIy+bitLYSP+ZEenHg36i23V2SGEz+kqjksg0uOGZ5LPiqg==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.10.tgz", + "integrity": "sha512-H5EZCoZaggEUQy8ocKsF7WAToGuZhjJlLvM3XOef46CbdIgbNeQ1p32N1PCuCjkVYwrAVOSMacN6CXXgIzuspg==", "dev": true, "requires": { "@polka/url": "^1.0.0-next.9", @@ -12909,9 +12990,9 @@ } }, "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", "dev": true }, "spdx-correct": { @@ -12979,9 +13060,9 @@ } }, "ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", + "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", "dev": true, "requires": { "minipass": "^3.1.1" @@ -13157,9 +13238,9 @@ } }, "streamr-test-utils": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/streamr-test-utils/-/streamr-test-utils-1.3.1.tgz", - "integrity": "sha512-3ol+bRQOSUz6Qjso0/VDBNzTxD9msizCOtsHgx7YqGYkxtIUSlGbsVtZh3JhBnnm53BNyaNHS74+N7Mjoa+w5Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/streamr-test-utils/-/streamr-test-utils-1.3.0.tgz", + "integrity": "sha512-mSjtHIUyoVksv6OXVDMj5BecbEXCKfXRmSmiHav1kJLEHj4oE9cF1N0Ml1I3KL3ARw51+RloRnPtRSexg71m/Q==", "dev": true }, "strict-event-emitter-types": { @@ -14136,15 +14217,15 @@ } }, "webcrypto-core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.2.0.tgz", - "integrity": "sha512-p76Z/YLuE4CHCRdc49FB/ETaM4bzM3roqWNJeGs+QNY1fOTzKTOVnhmudW1fuO+5EZg6/4LG9NJ6gaAyxTk9XQ==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.1.8.tgz", + "integrity": "sha512-hKnFXsqh0VloojNeTfrwFoRM4MnaWzH6vtXcaFcGjPEu+8HmBdQZnps3/2ikOFqS8bJN1RYr6mI2P/FJzyZnXg==", "requires": { - "@peculiar/asn1-schema": "^2.0.27", + "@peculiar/asn1-schema": "^2.0.12", "@peculiar/json-schema": "^1.1.12", "asn1js": "^2.0.26", - "pvtsutils": "^1.1.2", - "tslib": "^2.1.0" + "pvtsutils": "^1.0.11", + "tslib": "^2.0.1" } }, "webidl-conversions": { @@ -14489,17 +14570,17 @@ } }, "webpack-cli": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.5.0.tgz", - "integrity": "sha512-wXg/ef6Ibstl2f50mnkcHblRPN/P9J4Nlod5Hg9HGFgSeF8rsqDGHJeVe4aR26q9l62TUJi6vmvC2Qz96YJw1Q==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.4.0.tgz", + "integrity": "sha512-/Qh07CXfXEkMu5S8wEpjuaw2Zj/CC0hf/qbTDp6N8N7JjdGuaOjZ7kttz+zhuJO/J5m7alQEhNk9lsc4rC6xgQ==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.0.1", - "@webpack-cli/info": "^1.2.2", - "@webpack-cli/serve": "^1.3.0", + "@webpack-cli/configtest": "^1.0.0", + "@webpack-cli/info": "^1.2.1", + "@webpack-cli/serve": "^1.2.2", "colorette": "^1.2.1", - "commander": "^7.0.0", + "commander": "^6.2.0", "enquirer": "^2.3.6", "execa": "^5.0.0", "fastest-levenshtein": "^1.0.12", @@ -14511,9 +14592,9 @@ }, "dependencies": { "commander": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.0.0.tgz", - "integrity": "sha512-ovx/7NkTrnPuIV8sqk/GjUIIM1+iUQeqA3ye2VNpq9sVoiZsooObWlQy+OPWGI17GDaEoybuAGJm6U8yC077BA==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", "dev": true }, "execa": { @@ -14769,9 +14850,9 @@ } }, "ws": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz", - "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==" + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index c7ce34140..119b4bae7 100644 --- a/package.json +++ b/package.json @@ -29,9 +29,9 @@ "test-unit": "jest test/unit --detectOpenHandles", "coverage": "jest --coverage", "test-integration": "jest --forceExit test/integration", - "test-integration-no-resend": "jest --testTimeout=10000 --testPathIgnorePatterns='resend|Resend' --testNamePattern='^((?!(resend|Resend|resent|Resent|gap|Gap)).)*$' test/integration/*.test.js", - "test-integration-resend": "jest --testTimeout=15000 --testNamePattern='(resend|Resend|resent|Resent)' test/integration/*.test.js", - "test-integration-dataunions": "jest --testTimeout=15000 --runInBand test/integration/DataUnionEndpoints", + "test-integration-no-resend": "jest --forceExit --testTimeout=10000 --testPathIgnorePatterns='resend|Resend' --testNamePattern='^((?!(resend|Resend|resent|Resent|gap|Gap)).)*$' test/integration/*.test.js", + "test-integration-resend": "jest --forceExit --testTimeout=15000 --testNamePattern='(resend|Resend|resent|Resent)' test/integration/*.test.js", + "test-integration-dataunions": "jest --forceExit --testTimeout=15000 --runInBand test/integration/dataunion", "test-flakey": "jest --forceExit test/flakey/*", "test-browser": "node ./test/browser/server.js & node node_modules/nightwatch/bin/nightwatch ./test/browser/browser.js && pkill -f server.js", "install-example": "cd examples/webpack && npm ci", @@ -53,6 +53,7 @@ "@babel/preset-env": "^7.12.11", "@babel/preset-typescript": "^7.12.13", "@types/debug": "^4.1.5", + "@types/jest": "^26.0.20", "@types/qs": "^6.9.5", "@typescript-eslint/eslint-plugin": "^4.15.1", "@typescript-eslint/parser": "^4.15.1", @@ -87,6 +88,7 @@ }, "#IMPORTANT": "babel-runtime must be in dependencies, not devDependencies", "dependencies": { + "@ethersproject/abi": "^5.0.12", "@babel/runtime": "^7.12.13", "@babel/runtime-corejs3": "^7.12.13", "@ethersproject/address": "^5.0.9", diff --git a/src/Config.ts b/src/Config.ts index f0e43c183..711871e92 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -8,7 +8,7 @@ import { StreamrClientOptions } from './StreamrClient' const { ControlMessage } = ControlLayer const { StreamMessage } = MessageLayer -export default function ClientConfig(opts: StreamrClientOptions = {}) { +export default function ClientConfig(opts: Partial = {}) { const { id = counterId('StreamrClient') } = opts const options: StreamrClientOptions = { @@ -46,16 +46,10 @@ export default function ClientConfig(opts: StreamrClientOptions = {}) { // timeout: // pollingInterval: }, - // @ts-expect-error - dataUnion: null, // Give a "default target" of all data union endpoint operations (no need to pass argument every time) tokenAddress: '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', minimumWithdrawTokenWei: '1000000', // Threshold value set in AMB configs, smallest token amount to pass over the bridge - // @ts-expect-error - sidechainTokenAddress: null, // TODO // sidechain token - // @ts-expect-error - factoryMainnetAddress: null, // TODO // Data Union factory that creates a new Data Union - // @ts-expect-error - sidechainAmbAddress: null, // Arbitrary Message-passing Bridge (AMB), see https://github.com/poanetwork/tokenbridge + factoryMainnetAddress: 'TODO', // TODO define this value when we know it // Data Union factory that creates a new Data Union + factorySidechainAddress: 'TODO', // TODO define this value when we know it payForSignatureTransport: true, // someone must pay for transporting the withdraw tx to mainnet, either us or bridge operator ...opts, cache: { diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index f6060c3a8..f104228f6 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -14,10 +14,12 @@ import { getUserId } from './user' import { Todo } from './types' import { StreamEndpoints, StreamListQuery } from './rest/StreamEndpoints' import { LoginEndpoints } from './rest/LoginEndpoints' -import { DataUnionEndpoints, DataUnionOptions } from './rest/DataUnionEndpoints' +import { DataUnionEndpoints } from './rest/DataUnionEndpoints' import { BigNumber } from '@ethersproject/bignumber' import Stream, { StreamProperties } from './stream' import { ExternalProvider, JsonRpcFetchFunc } from '@ethersproject/providers' +import { DataUnion, DataUnionDeployOptions } from './dataunion/DataUnion' +import { getAddress } from '@ethersproject/address' export interface StreamrClientOptions { id?: string @@ -47,12 +49,10 @@ export interface StreamrClientOptions { sidechain?: { url?: string }, - dataUnion?: string - tokenAddress?: string, + tokenAddress: string, minimumWithdrawTokenWei?: BigNumber|number|string, - sidechainTokenAddress?: string - factoryMainnetAddress?: string - sidechainAmbAddress?: string + factoryMainnetAddress: string + factorySidechainAddress: string payForSignatureTransport?: boolean cache?: { maxSize?: number, @@ -179,7 +179,7 @@ export default class StreamrClient extends EventEmitter { loginEndpoints: LoginEndpoints dataUnionEndpoints: DataUnionEndpoints - constructor(options: StreamrClientOptions = {}, connection?: StreamrConnection) { + constructor(options: Partial = {}, connection?: StreamrConnection) { super() this.id = counterId(`${this.constructor.name}:${uid}`) this.debug = Debug(this.id) @@ -398,8 +398,13 @@ export default class StreamrClient extends EventEmitter { return this.connection.enableAutoDisconnect(...args) } - getAddress() { - return this.ethereum.getAddress() + getAddress(): string { + const address = this.ethereum.getAddress() + if (address) { + return getAddress(address) + } else { + throw new Error('StreamrClient is not authenticated with private key') + } } async getPublisherId() { @@ -468,115 +473,21 @@ export default class StreamrClient extends EventEmitter { return this.loginEndpoints.getUserInfo() } - async calculateDataUnionMainnetAddress(dataUnionName: string, deployerAddress: string, options: DataUnionOptions) { - return this.dataUnionEndpoints.calculateDataUnionMainnetAddress(dataUnionName, deployerAddress, options) - } - - async calculateDataUnionSidechainAddress(duMainnetAddress: string, options: DataUnionOptions) { - return this.dataUnionEndpoints.calculateDataUnionSidechainAddress(duMainnetAddress, options) - } - - async deployDataUnion(options: DataUnionOptions = {}) { - return this.dataUnionEndpoints.deployDataUnion(options) - } - - async getDataUnionContract(options: DataUnionOptions = {}) { - return this.dataUnionEndpoints.getDataUnionContract(options) - } - - async createSecret(dataUnionMainnetAddress: string, name: string = 'Untitled Data Union Secret') { - return this.dataUnionEndpoints.createSecret(dataUnionMainnetAddress, name) - } - - async kick(memberAddressList: string[], options: DataUnionOptions = {}) { - return this.dataUnionEndpoints.kick(memberAddressList, options) - } - - async addMembers(memberAddressList: string[], options: DataUnionOptions = {}) { - return this.dataUnionEndpoints.addMembers(memberAddressList, options) - } - - async withdrawMember(memberAddress: string, options: DataUnionOptions) { - return this.dataUnionEndpoints.withdrawMember(memberAddress, options) - } - - async getWithdrawMemberTx(memberAddress: string, options: DataUnionOptions) { - return this.dataUnionEndpoints.getWithdrawMemberTx(memberAddress, options) - } - - async withdrawToSigned(memberAddress: string, recipientAddress: string, signature: string, options: DataUnionOptions) { - return this.dataUnionEndpoints.withdrawToSigned(memberAddress, recipientAddress, signature, options) - } - - async getWithdrawToSignedTx(memberAddress: string, recipientAddress: string, signature: string, options: DataUnionOptions) { - return this.dataUnionEndpoints.getWithdrawToSignedTx(memberAddress, recipientAddress, signature, options) - } - - async setAdminFee(newFeeFraction: number, options: DataUnionOptions) { - return this.dataUnionEndpoints.setAdminFee(newFeeFraction, options) - } - - async getAdminFee(options: DataUnionOptions) { - return this.dataUnionEndpoints.getAdminFee(options) - } - - async getAdminAddress(options: DataUnionOptions) { - return this.dataUnionEndpoints.getAdminAddress(options) - } - - async joinDataUnion(options: DataUnionOptions = {}) { - return this.dataUnionEndpoints.joinDataUnion(options) - } - - async hasJoined(memberAddress: string, options: DataUnionOptions = {}) { - return this.dataUnionEndpoints.hasJoined(memberAddress, options) - } - - async getMembers(options: DataUnionOptions) { - return this.dataUnionEndpoints.getMembers(options) - } - - async getDataUnionStats(options: DataUnionOptions) { - return this.dataUnionEndpoints.getDataUnionStats(options) - } - - async getMemberStats(memberAddress: string, options: DataUnionOptions) { - return this.dataUnionEndpoints.getMemberStats(memberAddress, options) - } - - async getMemberBalance(memberAddress: string, options: DataUnionOptions) { - return this.dataUnionEndpoints.getMemberBalance(memberAddress, options) - } - - async getTokenBalance(address: string|null|undefined, options: DataUnionOptions) { - return this.dataUnionEndpoints.getTokenBalance(address, options) - } - - async getDataUnionVersion(contractAddress: string) { - return this.dataUnionEndpoints.getDataUnionVersion(contractAddress) - } - - async withdraw(options: DataUnionOptions = {}) { - return this.dataUnionEndpoints.withdraw(options) - } - - async getWithdrawTx(options: DataUnionOptions) { - return this.dataUnionEndpoints.getWithdrawTx(options) - } - - async withdrawTo(recipientAddress: string, options: DataUnionOptions = {}) { - return this.dataUnionEndpoints.withdrawTo(recipientAddress, options) + async getTokenBalance(address: string) { + return this.dataUnionEndpoints.getTokenBalance(address) } - async getWithdrawTxTo(recipientAddress: string, options: DataUnionOptions) { - return this.dataUnionEndpoints.getWithdrawTxTo(recipientAddress, options) + getDataUnion(contractAddress: string) { + return new DataUnion(contractAddress, undefined, this.dataUnionEndpoints) } - async signWithdrawTo(recipientAddress: string, options: DataUnionOptions) { - return this.dataUnionEndpoints.signWithdrawTo(recipientAddress, options) + async deployDataUnion(options?: DataUnionDeployOptions) { + const contract = await this.dataUnionEndpoints.deployDataUnion(options) + return new DataUnion(contract.address, contract.sidechain.address, this.dataUnionEndpoints) } - async signWithdrawAmountTo(recipientAddress: string, amountTokenWei: BigNumber|number|string, options: DataUnionOptions) { - return this.dataUnionEndpoints.signWithdrawAmountTo(recipientAddress, amountTokenWei, options) + _getDataUnionFromName({ dataUnionName, deployerAddress }: { dataUnionName: string, deployerAddress: string}) { + const contractAddress = this.dataUnionEndpoints.calculateDataUnionMainnetAddress(dataUnionName, deployerAddress) + return this.getDataUnion(contractAddress) } } diff --git a/src/dataunion/DataUnion.ts b/src/dataunion/DataUnion.ts new file mode 100644 index 000000000..a336f2f16 --- /dev/null +++ b/src/dataunion/DataUnion.ts @@ -0,0 +1,139 @@ +import { BigNumber } from '@ethersproject/bignumber' +import { DataUnionEndpoints } from '../rest/DataUnionEndpoints' + +export interface DataUnionDeployOptions { + owner?: string, + joinPartAgents?: string[], + dataUnionName?: string, + adminFee?: number, + sidechainPollingIntervalMs?: number, + sidechainRetryTimeoutMs?: number + confirmations?: number + gasPrice?: BigNumber +} + +export enum JoinRequestState { + PENDING = 'PENDING', + ACCEPTED = 'ACCEPTED', + REJECTED = 'REJECTED' +} + +export interface JoinResponse { + id: string + state: JoinRequestState +} + +export interface DataUnionWithdrawOptions { + pollingIntervalMs?: number + retryTimeoutMs?: number + payForSignatureTransport?: boolean +} + +export interface DataUnionMemberListModificationOptions { + confirmations?: number +} + +export class DataUnion { + + contractAddress: string + sidechainAddress: string + dataUnionEndpoints: DataUnionEndpoints + + constructor(contractAddress: string, sidechainAddress: string|undefined, dataUnionEndpoints: DataUnionEndpoints) { + this.contractAddress = contractAddress + this.sidechainAddress = sidechainAddress || dataUnionEndpoints.calculateDataUnionSidechainAddress(contractAddress) + this.dataUnionEndpoints = dataUnionEndpoints + } + + getAddress() { + return this.contractAddress + } + + getSidechainAddress() { + return this.sidechainAddress + } + + // Member functions + + async join(secret?: string) { + return this.dataUnionEndpoints.join(secret, this.contractAddress) + } + + async isMember(memberAddress: string) { + return this.dataUnionEndpoints.isMember(memberAddress, this.contractAddress) + } + + async withdrawAll(options?: DataUnionWithdrawOptions) { + return this.dataUnionEndpoints.withdrawAll(this.contractAddress, options) + } + + async withdrawAllTo(recipientAddress: string, options?: DataUnionWithdrawOptions) { + return this.dataUnionEndpoints.withdrawAllTo(recipientAddress, options, this.contractAddress) + } + + async signWithdrawAllTo(recipientAddress: string) { + return this.dataUnionEndpoints.signWithdrawAllTo(recipientAddress, this.contractAddress) + } + + async signWithdrawAmountTo(recipientAddress: string, amountTokenWei: BigNumber|number|string) { + return this.dataUnionEndpoints.signWithdrawAmountTo(recipientAddress, amountTokenWei, this.contractAddress) + } + + // Query functions + + async getStats() { + return this.dataUnionEndpoints.getStats(this.contractAddress) + } + + async getMemberStats(memberAddress: string) { + return this.dataUnionEndpoints.getMemberStats(memberAddress, this.contractAddress) + } + + async getWithdrawableEarnings(memberAddress: string) { + return this.dataUnionEndpoints.getWithdrawableEarnings(memberAddress, this.contractAddress) + } + + async getAdminFee() { + return this.dataUnionEndpoints.getAdminFee(this.contractAddress) + } + + async getAdminAddress() { + return this.dataUnionEndpoints.getAdminAddress(this.contractAddress) + } + + async getVersion() { + return this.dataUnionEndpoints.getVersion(this.contractAddress) + } + + // Admin functions + + async createSecret(name: string = 'Untitled Data Union Secret') { + return this.dataUnionEndpoints.createSecret(this.contractAddress, name) + } + + async addMembers(memberAddressList: string[], options?: DataUnionMemberListModificationOptions) { + return this.dataUnionEndpoints.addMembers(memberAddressList, options, this.contractAddress) + } + + async removeMembers(memberAddressList: string[], options?: DataUnionMemberListModificationOptions) { + return this.dataUnionEndpoints.removeMembers(memberAddressList, options, this.contractAddress) + } + + async withdrawAllToMember(memberAddress: string, options?: DataUnionWithdrawOptions) { + return this.dataUnionEndpoints.withdrawAllToMember(memberAddress, options, this.contractAddress) + } + + async withdrawAllToSigned(memberAddress: string, recipientAddress: string, signature: string, options?: DataUnionWithdrawOptions) { + return this.dataUnionEndpoints.withdrawAllToSigned(memberAddress, recipientAddress, signature, options, this.contractAddress) + } + + async setAdminFee(newFeeFraction: number) { + return this.dataUnionEndpoints.setAdminFee(newFeeFraction, this.contractAddress) + } + + // Internal functions + + async _getContract() { + return this.dataUnionEndpoints.getContract(this.contractAddress) + } +} diff --git a/src/rest/DataUnionEndpoints.ts b/src/rest/DataUnionEndpoints.ts index 73c3bfa72..2ebc45e04 100644 --- a/src/rest/DataUnionEndpoints.ts +++ b/src/rest/DataUnionEndpoints.ts @@ -5,18 +5,21 @@ * ABIs * helper utils * admin: DEPLOY AND SETUP DATA UNION Functions for deploying the contract and adding secrets for smooth joining - * admin: MANAGE DATA UNION Kick and add members + * admin: MANAGE DATA UNION add and part members * member: JOIN & QUERY DATA UNION Publicly available info about dataunions and their members (with earnings and proofs) * member: WITHDRAW EARNINGS Withdrawing functions, there's many: normal, agent, donate */ -import { getAddress, isAddress } from '@ethersproject/address' +import { getAddress, getCreate2Address, isAddress } from '@ethersproject/address' import { BigNumber } from '@ethersproject/bignumber' import { arrayify, hexZeroPad } from '@ethersproject/bytes' import { Contract } from '@ethersproject/contracts' import { keccak256 } from '@ethersproject/keccak256' +import { defaultAbiCoder } from '@ethersproject/abi' +import { TransactionReceipt, TransactionResponse } from '@ethersproject/providers' import { verifyMessage } from '@ethersproject/wallet' import debug from 'debug' +import { DataUnionDeployOptions, DataUnionMemberListModificationOptions, DataUnionWithdrawOptions } from '../dataunion/DataUnion' import StreamrClient from '../StreamrClient' import { Todo } from '../types' @@ -24,18 +27,21 @@ import { until, getEndpointUrl } from '../utils' import authFetch from './authFetch' -export interface DataUnionOptions { - wallet?: Todo, - provider?: Todo, - confirmations?: Todo, - gasPrice?: Todo, - dataUnion?: Todo, - tokenAddress?: Todo, - minimumWithdrawTokenWei?: BigNumber|number|string, - sidechainTokenAddress?: string, - factoryMainnetAddress?: string, - sidechainAmbAddress?: string, - payForSignatureTransport?: boolean +export interface DataUnionStats { + activeMemberCount: Todo, + inactiveMemberCount: Todo, + joinPartAgentCount: Todo, + totalEarnings: Todo, + totalWithdrawable: Todo, + lifetimeMemberEarnings: Todo +} + +export interface MemberStats { + status: Todo + earningsBeforeLastJoin: Todo + lmeAtJoin: Todo + totalEarnings: Todo + withdrawableEarnings: Todo } const log = debug('StreamrClient::DataUnionEndpoints') @@ -283,29 +289,16 @@ function throwIfBadAddress(address: string, variableDescription: Todo) { } } -/** - * Parse address, or use this client's auth address if input not given - * @param {StreamrClient} this - * @param {EthereumAddress} inputAddress from user (NOT case sensitive) - * @returns {EthereumAddress} with checksum case - */ -function parseAddress(client: StreamrClient, inputAddress: string|null|undefined) { - if (inputAddress && isAddress(inputAddress)) { - return getAddress(inputAddress) - } - return client.getAddress() -} - // Find the Asyncronous Message-passing Bridge sidechain ("home") contract let cachedSidechainAmb: Todo -async function getSidechainAmb(client: StreamrClient, options: DataUnionOptions) { +async function getSidechainAmb(client: StreamrClient) { if (!cachedSidechainAmb) { const getAmbPromise = async () => { const mainnetProvider = client.ethereum.getMainnetProvider() - const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress + const factoryMainnetAddress = client.options.factoryMainnetAddress const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, mainnetProvider) const sidechainProvider = client.ethereum.getSidechainProvider() - const factorySidechainAddress = await factoryMainnet.data_union_sidechain_factory() + const factorySidechainAddress = await factoryMainnet.data_union_sidechain_factory() // TODO use getDataUnionSidechainAddress() const factorySidechain = new Contract(factorySidechainAddress, [{ name: 'amb', inputs: [], @@ -324,16 +317,16 @@ async function getSidechainAmb(client: StreamrClient, options: DataUnionOptions) return cachedSidechainAmb } -async function getMainnetAmb(client: StreamrClient, options: DataUnionOptions) { +async function getMainnetAmb(client: StreamrClient) { const mainnetProvider = client.ethereum.getMainnetProvider() - const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress + const factoryMainnetAddress = client.options.factoryMainnetAddress const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, mainnetProvider) const mainnetAmbAddress = await factoryMainnet.amb() return new Contract(mainnetAmbAddress, mainnetAmbABI, mainnetProvider) } -async function requiredSignaturesHaveBeenCollected(client: StreamrClient, messageHash: Todo, options: DataUnionOptions = {}) { - const sidechainAmb = await getSidechainAmb(client, options) +async function requiredSignaturesHaveBeenCollected(client: StreamrClient, messageHash: Todo) { + const sidechainAmb = await getSidechainAmb(client) const requiredSignatureCount = await sidechainAmb.requiredSignatures() // Bit 255 is set to mark completion, double check though @@ -347,8 +340,8 @@ async function requiredSignaturesHaveBeenCollected(client: StreamrClient, messag } // move signatures from sidechain to mainnet -async function transportSignatures(client: StreamrClient, messageHash: Todo, options: DataUnionOptions) { - const sidechainAmb = await getSidechainAmb(client, options) +async function transportSignatures(client: StreamrClient, messageHash: Todo) { + const sidechainAmb = await getSidechainAmb(client) const message = await sidechainAmb.message(messageHash) const messageId = '0x' + message.substr(2, 64) const sigCountStruct = await sidechainAmb.numMessagesSigned(messageHash) @@ -368,7 +361,7 @@ async function transportSignatures(client: StreamrClient, messageHash: Todo, opt log(`All signatures packed into one: ${packedSignatures}`) // Gas estimation also checks that the transaction would succeed, and provides a helpful error message in case it would fail - const mainnetAmb = await getMainnetAmb(client, options) + const mainnetAmb = await getMainnetAmb(client) log(`Estimating gas using mainnet AMB @ ${mainnetAmb.address}, message=${message}`) let gasLimit try { @@ -435,19 +428,19 @@ async function transportSignatures(client: StreamrClient, messageHash: Todo, opt // client could be replaced with AMB (mainnet and sidechain) async function untilWithdrawIsComplete( client: StreamrClient, - getWithdrawTxFunc: (options: DataUnionOptions) => Todo, - getBalanceFunc: (options: DataUnionOptions) => Todo, - options: DataUnionOptions = {} + getWithdrawTxFunc: () => Promise, + getBalanceFunc: () => Promise, + options: DataUnionWithdrawOptions = {} ) { const { pollingIntervalMs = 1000, retryTimeoutMs = 60000, }: Todo = options - const balanceBefore = await getBalanceFunc(options) - const tx = await getWithdrawTxFunc(options) + const balanceBefore = await getBalanceFunc() + const tx = await getWithdrawTxFunc() const tr = await tx.wait() - if (options.payForSignatureTransport) { + if (options.payForSignatureTransport || client.options.payForSignatureTransport) { log(`Got receipt, filtering UserRequestForSignature from ${tr.events.length} events...`) // event UserRequestForSignature(bytes32 indexed messageId, bytes encodedData); const sigEventArgsArray = tr.events.filter((e: Todo) => e.event === 'UserRequestForSignature').map((e: Todo) => e.args) @@ -461,10 +454,10 @@ async function untilWithdrawIsComplete( const messageHash = keccak256(eventArgs[1]) log(`Waiting until sidechain AMB has collected required signatures for hash=${messageHash}...`) - await until(async () => requiredSignaturesHaveBeenCollected(client, messageHash, options), pollingIntervalMs, retryTimeoutMs) + await until(async () => requiredSignaturesHaveBeenCollected(client, messageHash), pollingIntervalMs, retryTimeoutMs) log(`Checking mainnet AMB hasn't already processed messageId=${messageId}`) - const mainnetAmb = await getMainnetAmb(client, options) + const mainnetAmb = await getMainnetAmb(client) const alreadySent = await mainnetAmb.messageCallStatus(messageId) const failAddress = await mainnetAmb.failedMessageSender(messageId) if (alreadySent || failAddress !== '0x0000000000000000000000000000000000000000') { // zero address means no failed messages @@ -477,25 +470,24 @@ async function untilWithdrawIsComplete( } log(`Transporting signatures for hash=${messageHash}`) - await transportSignatures(client, messageHash, options) + await transportSignatures(client, messageHash) } /* eslint-enable no-await-in-loop */ } log(`Waiting for balance ${balanceBefore.toString()} to change`) - await until(async () => !(await getBalanceFunc(options)).eq(balanceBefore), retryTimeoutMs, pollingIntervalMs) + await until(async () => !(await getBalanceFunc()).eq(balanceBefore), retryTimeoutMs, pollingIntervalMs) return tr } -// TODO: calculate addresses in JS instead of asking over RPC, see data-union-solidity/contracts/CloneLib.sol -// key the cache with name only, since PROBABLY one StreamrClient will ever use only one private key +// TODO remove caching as we calculate the values only when deploying the DU const mainnetAddressCache: Todo = {} // mapping: "name" -> mainnet address /** @returns {Promise} Mainnet address for Data Union */ -async function getDataUnionMainnetAddress(client: StreamrClient, dataUnionName: string, deployerAddress: string, options: DataUnionOptions = {}) { +async function fetchDataUnionMainnetAddress(client: StreamrClient, dataUnionName: string, deployerAddress: string) { if (!mainnetAddressCache[dataUnionName]) { const provider = client.ethereum.getMainnetProvider() - const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress + const factoryMainnetAddress = client.options.factoryMainnetAddress const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, provider) const addressPromise = factoryMainnet.mainnetAddress(deployerAddress, dataUnionName) mainnetAddressCache[dataUnionName] = addressPromise @@ -504,13 +496,21 @@ async function getDataUnionMainnetAddress(client: StreamrClient, dataUnionName: return mainnetAddressCache[dataUnionName] } -// TODO: calculate addresses in JS +function getDataUnionMainnetAddress(client: StreamrClient, dataUnionName: string, deployerAddress: string) { + const factoryMainnetAddress = client.options.factoryMainnetAddress + // NOTE! this must be updated when DU sidechain smartcontract changes: keccak256(CloneLib.cloneBytecode(data_union_mainnet_template)); + const codeHash = '0x50a78bac973bdccfc8415d7d9cfd62898b8f7cf6e9b3a15e7d75c0cb820529eb' + const salt = keccak256(defaultAbiCoder.encode(['string', 'address'], [dataUnionName, deployerAddress])) + return getCreate2Address(factoryMainnetAddress, salt, codeHash) +} + +// TODO remove caching as we calculate the values only when deploying the DU const sidechainAddressCache: Todo = {} // mapping: mainnet address -> sidechain address /** @returns {Promise} Sidechain address for Data Union */ -async function getDataUnionSidechainAddress(client: StreamrClient, duMainnetAddress: string, options: DataUnionOptions = {}) { +async function fetchDataUnionSidechainAddress(client: StreamrClient, duMainnetAddress: string) { if (!sidechainAddressCache[duMainnetAddress]) { const provider = client.ethereum.getMainnetProvider() - const factoryMainnetAddress = options.factoryMainnetAddress || client.options.factoryMainnetAddress + const factoryMainnetAddress = client.options.factoryMainnetAddress const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, provider) const addressPromise = factoryMainnet.sidechainAddress(duMainnetAddress) sidechainAddressCache[duMainnetAddress] = addressPromise @@ -519,40 +519,42 @@ async function getDataUnionSidechainAddress(client: StreamrClient, duMainnetAddr return sidechainAddressCache[duMainnetAddress] } -function getMainnetContractReadOnly(client: StreamrClient, options: DataUnionOptions = {}) { - // @ts-expect-error - let dataUnion = options.dataUnion || options.dataUnionAddress || client.options.dataUnion - if (isAddress(dataUnion)) { - const provider = client.ethereum.getMainnetProvider() - dataUnion = new Contract(dataUnion, dataUnionMainnetABI, provider) - } +function getDataUnionSidechainAddress(client: StreamrClient, mainnetAddress: string) { + const factorySidechainAddress = client.options.factorySidechainAddress + // NOTE! this must be updated when DU sidechain smartcontract changes: keccak256(CloneLib.cloneBytecode(data_union_sidechain_template)) + const codeHash = '0x040cf686e25c97f74a23a4bf01c29dd77e260c4b694f5611017ce9713f58de83' + return getCreate2Address(factorySidechainAddress, hexZeroPad(mainnetAddress, 32), codeHash) +} - if (!(dataUnion instanceof Contract)) { - throw new Error(`Option dataUnion=${dataUnion} was not a good Ethereum address or Contract`) +function getMainnetContractReadOnly(contractAddress: string, client: StreamrClient) { + if (isAddress(contractAddress)) { + const provider = client.ethereum.getMainnetProvider() + return new Contract(contractAddress, dataUnionMainnetABI, provider) + } else { + throw new Error(`${contractAddress} was not a good Ethereum address`) } - return dataUnion } -function getMainnetContract(client: StreamrClient, options: DataUnionOptions = {}) { - const du = getMainnetContractReadOnly(client, options) +function getMainnetContract(contractAddress: string, client: StreamrClient) { + const du = getMainnetContractReadOnly(contractAddress, client) const signer = client.ethereum.getSigner() // @ts-expect-error return du.connect(signer) } -async function getSidechainContract(client: StreamrClient, options: DataUnionOptions = {}) { +async function getSidechainContract(contractAddress: string, client: StreamrClient) { const signer = await client.ethereum.getSidechainSigner() - const duMainnet = getMainnetContractReadOnly(client, options) - const duSidechainAddress = await getDataUnionSidechainAddress(client, duMainnet.address, options) + const duMainnet = getMainnetContractReadOnly(contractAddress, client) + const duSidechainAddress = getDataUnionSidechainAddress(client, duMainnet.address) // @ts-expect-error const duSidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, signer) return duSidechain } -async function getSidechainContractReadOnly(client: StreamrClient, options: DataUnionOptions = {}) { +async function getSidechainContractReadOnly(contractAddress: string, client: StreamrClient) { const provider = await client.ethereum.getSidechainProvider() - const duMainnet = getMainnetContractReadOnly(client, options) - const duSidechainAddress = await getDataUnionSidechainAddress(client, duMainnet.address, options) + const duMainnet = getMainnetContractReadOnly(contractAddress, client) + const duSidechainAddress = getDataUnionSidechainAddress(client, duMainnet.address) // @ts-expect-error const duSidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, provider) return duSidechain @@ -570,47 +572,24 @@ export class DataUnionEndpoints { // admin: DEPLOY AND SETUP DATA UNION // ////////////////////////////////////////////////////////////////// - async calculateDataUnionMainnetAddress(dataUnionName: string, deployerAddress: string, options: DataUnionOptions) { + // TODO inline this function? + calculateDataUnionMainnetAddress(dataUnionName: string, deployerAddress: string) { const address = getAddress(deployerAddress) // throws if bad address - return getDataUnionMainnetAddress(this.client, dataUnionName, address, options) + return getDataUnionMainnetAddress(this.client, dataUnionName, address) } - async calculateDataUnionSidechainAddress(duMainnetAddress: string, options: DataUnionOptions) { + // TODO inline this function? + calculateDataUnionSidechainAddress(duMainnetAddress: string) { const address = getAddress(duMainnetAddress) // throws if bad address - return getDataUnionSidechainAddress(this.client, address, options) + return getDataUnionSidechainAddress(this.client, address) } - /** - * TODO: update this comment - * @typedef {object} EthereumOptions all optional, hence "options" - * @property {Wallet | string} wallet or private key, default is currently logged in StreamrClient (if auth: privateKey) - * @property {string} key private key, alias for String wallet - * @property {string} privateKey, alias for String wallet - * @property {providers.Provider} provider to use in case wallet was a String, or omitted - * @property {number} confirmations, default is 1 - * @property {BigNumber} gasPrice in wei (part of ethers overrides), default is whatever the network recommends (ethers.js default) - * @see https://docs.ethers.io/ethers.js/html/api-contract.html#overrides - */ - /** - * @typedef {object} AdditionalDeployOptions for deployDataUnion - * @property {EthereumAddress} owner new data union owner, defaults to StreamrClient authenticated user - * @property {Array} joinPartAgents defaults to just the owner - * @property {number} adminFee fraction (number between 0...1 where 1 means 100%) - * @property {EthereumAddress} factoryMainnetAddress defaults to StreamrClient options - * @property {string} dataUnionName unique (to the DataUnionFactory) identifier of the new data union, must not exist yet - */ - /** - * @typedef {EthereumOptions & AdditionalDeployOptions} DeployOptions - */ - // TODO: gasPrice to overrides (not needed for browser, but would be useful in node.js) - /** * Create a new DataUnionMainnet contract to mainnet with DataUnionFactoryMainnet * This triggers DataUnionSidechain contract creation in sidechain, over the bridge (AMB) - * @param {DeployOptions} options such as adminFee (default: 0) * @return {Promise} that resolves when the new DU is deployed over the bridge to side-chain */ - async deployDataUnion(options: DataUnionOptions = {}) { + async deployDataUnion(options: DataUnionDeployOptions = {}): Promise { const { owner, joinPartAgents, @@ -618,7 +597,9 @@ export class DataUnionEndpoints { adminFee = 0, sidechainPollingIntervalMs = 1000, sidechainRetryTimeoutMs = 600000, - }: Todo = options + confirmations = 1, + gasPrice + } = options let duName = dataUnionName if (!duName) { @@ -633,8 +614,7 @@ export class DataUnionEndpoints { const mainnetWallet = this.client.ethereum.getSigner() const sidechainProvider = this.client.ethereum.getSidechainProvider() - // parseAddress defaults to authenticated user (also if "owner" is not an address) - const ownerAddress = parseAddress(this.client, owner) + const ownerAddress = (owner) ? getAddress(owner) : this.client.getAddress() let agentAddressList if (Array.isArray(joinPartAgents)) { @@ -648,16 +628,17 @@ export class DataUnionEndpoints { } } + const deployerAddress = this.client.getAddress() // @ts-expect-error - const duMainnetAddress = await getDataUnionMainnetAddress(this.client, duName, ownerAddress, options) - const duSidechainAddress = await getDataUnionSidechainAddress(this.client, duMainnetAddress, options) + const duMainnetAddress = await fetchDataUnionMainnetAddress(this.client, duName, deployerAddress, options) + const duSidechainAddress = await fetchDataUnionSidechainAddress(this.client, duMainnetAddress) if (await mainnetProvider.getCode(duMainnetAddress) !== '0x') { throw new Error(`Mainnet data union "${duName}" contract ${duMainnetAddress} already exists!`) } const factoryMainnetAddress = throwIfBadAddress( - (options.factoryMainnetAddress || this.client.options.factoryMainnetAddress)!, + this.client.options.factoryMainnetAddress!, 'StreamrClient.options.factoryMainnetAddress' ) if (await mainnetProvider.getCode(factoryMainnetAddress) === '0x') { @@ -667,13 +648,18 @@ export class DataUnionEndpoints { // function deployNewDataUnion(address owner, uint256 adminFeeFraction, address[] agents, string duName) // @ts-expect-error const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, mainnetWallet) + const ethersOptions: any = {} + if (gasPrice) { + ethersOptions.gasPrice = gasPrice + } const tx = await factoryMainnet.deployNewDataUnion( ownerAddress, adminFeeBN, agentAddressList, duName, + ethersOptions ) - const tr = await tx.wait() + const tr = await tx.wait(confirmations) log(`Data Union "${duName}" (mainnet: ${duMainnetAddress}, sidechain: ${duSidechainAddress}) deployed to mainnet, waiting for side-chain...`) await until( @@ -692,20 +678,17 @@ export class DataUnionEndpoints { return dataUnion } - async getDataUnionContract(options: DataUnionOptions = {}) { - const ret = getMainnetContract(this.client, options) + async getContract(contractAddress: string) { + const ret = getMainnetContract(contractAddress, this.client) // @ts-expect-error - ret.sidechain = await getSidechainContract(this.client, options) + ret.sidechain = await getSidechainContract(contractAddress, this.client) return ret } /** * Add a new data union secret - * @param {EthereumAddress} dataUnionMainnetAddress - * @param {String} name describes the secret - * @returns {String} the server-generated secret */ - async createSecret(dataUnionMainnetAddress: string, name: string = 'Untitled Data Union Secret') { + async createSecret(dataUnionMainnetAddress: string, name: string = 'Untitled Data Union Secret'): Promise { const duAddress = getAddress(dataUnionMainnetAddress) // throws if bad address const url = getEndpointUrl(this.client.options.restUrl, 'dataunions', duAddress, 'secrets') const res = await authFetch( @@ -729,45 +712,43 @@ export class DataUnionEndpoints { // ////////////////////////////////////////////////////////////////// /** - * Kick given members from data union - * @param {List} memberAddressList to kick - * @returns {Promise} partMembers sidechain transaction + * Add given Ethereum addresses as data union members */ - async kick(memberAddressList: string[], options: DataUnionOptions = {}) { + async addMembers(memberAddressList: string[], options: DataUnionMemberListModificationOptions|undefined = {}, contractAddress: string): Promise { const members = memberAddressList.map(getAddress) // throws if there are bad addresses - const duSidechain = await getSidechainContract(this.client, options) - const tx = await duSidechain.partMembers(members) + const duSidechain = await getSidechainContract(contractAddress, this.client) + const tx = await duSidechain.addMembers(members) // TODO: wrap promise for better error reporting in case tx fails (parse reason, throw proper error) - return tx.wait(options.confirmations || 1) + const { confirmations = 1 } = options + return tx.wait(confirmations) } /** - * Add given Ethereum addresses as data union members - * @param {List} memberAddressList to add - * @returns {Promise} addMembers sidechain transaction + * Remove given members from data union */ - async addMembers(memberAddressList: string[], options: DataUnionOptions = {}) { + async removeMembers(memberAddressList: string[], options: DataUnionMemberListModificationOptions|undefined = {}, contractAddress: string): Promise { const members = memberAddressList.map(getAddress) // throws if there are bad addresses - const duSidechain = await getSidechainContract(this.client, options) - const tx = await duSidechain.addMembers(members) + const duSidechain = await getSidechainContract(contractAddress, this.client) + const tx = await duSidechain.partMembers(members) // TODO: wrap promise for better error reporting in case tx fails (parse reason, throw proper error) - return tx.wait(options.confirmations || 1) + const { confirmations = 1 } = options + return tx.wait(confirmations) } /** * Admin: withdraw earnings (pay gas) on behalf of a member * TODO: add test * @param {EthereumAddress} memberAddress the other member who gets their tokens out of the Data Union - * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) + * @param options * @returns {Promise} get receipt once withdraw transaction is confirmed */ - async withdrawMember(memberAddress: string, options: DataUnionOptions) { + async withdrawAllToMember(memberAddress: string, options: DataUnionWithdrawOptions|undefined, contractAddress: string): Promise { const address = getAddress(memberAddress) // throws if bad address const tr = await untilWithdrawIsComplete( this.client, - (opts) => this.getWithdrawMemberTx(address, opts), - (opts) => this.getTokenBalance(address, opts), - { ...this.client.options, ...options } + () => this.getWithdrawAllToMemberTx(address, contractAddress), + () => this.getTokenBalance(address), + options ) return tr } @@ -775,78 +756,69 @@ export class DataUnionEndpoints { /** * Admin: get the tx promise for withdrawing all earnings on behalf of a member * @param {EthereumAddress} memberAddress the other member who gets their tokens out of the Data Union - * @param {EthereumAddress} dataUnion to withdraw my earnings from - * @param {EthereumOptions} options * @returns {Promise} await on call .wait to actually send the tx */ - async getWithdrawMemberTx(memberAddress: string, options: DataUnionOptions) { + async getWithdrawAllToMemberTx(memberAddress: string, contractAddress: string): Promise { const a = getAddress(memberAddress) // throws if bad address - const duSidechain = await getSidechainContract(this.client, options) + const duSidechain = await getSidechainContract(contractAddress, this.client) return duSidechain.withdrawAll(a, true) // sendToMainnet=true } /** * Admin: Withdraw a member's earnings to another address, signed by the member - * @param {EthereumAddress} dataUnion to withdraw my earnings from * @param {EthereumAddress} memberAddress the member whose earnings are sent out * @param {EthereumAddress} recipientAddress the address to receive the tokens in mainnet - * @param {string} signature from member, produced using signWithdrawTo - * @param {EthereumOptions} options + * @param {string} signature from member, produced using signWithdrawAllTo * @returns {Promise} get receipt once withdraw transaction is confirmed */ - async withdrawToSigned(memberAddress: string, recipientAddress: string, signature: string, options: DataUnionOptions) { + async withdrawAllToSigned(memberAddress: string, recipientAddress: string, signature: string, options: DataUnionWithdrawOptions|undefined, contractAddress: string): Promise { const from = getAddress(memberAddress) // throws if bad address const to = getAddress(recipientAddress) const tr = await untilWithdrawIsComplete( this.client, - (opts) => this.getWithdrawToSignedTx(from, to, signature, opts), - (opts) => this.getTokenBalance(to, opts), - { ...this.client.options, ...options } + () => this.getWithdrawAllToSignedTx(from, to, signature, contractAddress), + () => this.getTokenBalance(to), + options ) return tr } /** * Admin: Withdraw a member's earnings to another address, signed by the member - * @param {EthereumAddress} dataUnion to withdraw my earnings from * @param {EthereumAddress} memberAddress the member whose earnings are sent out * @param {EthereumAddress} recipientAddress the address to receive the tokens in mainnet - * @param {string} signature from member, produced using signWithdrawTo - * @param {EthereumOptions} options + * @param {string} signature from member, produced using signWithdrawAllTo * @returns {Promise} await on call .wait to actually send the tx */ - async getWithdrawToSignedTx(memberAddress: string, recipientAddress: string, signature: string, options: DataUnionOptions) { - const duSidechain = await getSidechainContract(this.client, options) + async getWithdrawAllToSignedTx(memberAddress: string, recipientAddress: string, signature: string, contractAddress: string): Promise { + const duSidechain = await getSidechainContract(contractAddress, this.client) return duSidechain.withdrawAllToSigned(memberAddress, recipientAddress, true, signature) // sendToMainnet=true } /** - * Admin: set admin fee for the data union - * @param {number} newFeeFraction between 0.0 and 1.0 - * @param {EthereumOptions} options + * Admin: set admin fee (between 0.0 and 1.0) for the data union */ - async setAdminFee(newFeeFraction: number, options: DataUnionOptions) { + async setAdminFee(newFeeFraction: number, contractAddress: string): Promise { if (newFeeFraction < 0 || newFeeFraction > 1) { throw new Error('newFeeFraction argument must be a number between 0...1, got: ' + newFeeFraction) } const adminFeeBN = BigNumber.from((newFeeFraction * 1e18).toFixed()) // last 2...3 decimals are going to be gibberish - const duMainnet = getMainnetContract(this.client, options) + const duMainnet = getMainnetContract(contractAddress, this.client) const tx = await duMainnet.setAdminFee(adminFeeBN) return tx.wait() } /** - * Get data union admin fee fraction that admin gets from each revenue event - * @returns {number} between 0.0 and 1.0 + * Get data union admin fee fraction (between 0.0 and 1.0) that admin gets from each revenue event */ - async getAdminFee(options: DataUnionOptions) { - const duMainnet = getMainnetContractReadOnly(this.client, options) + async getAdminFee(contractAddress: string): Promise { + const duMainnet = getMainnetContractReadOnly(contractAddress, this.client) const adminFeeBN = await duMainnet.adminFeeFraction() return +adminFeeBN.toString() / 1e18 } - async getAdminAddress(options: DataUnionOptions) { - const duMainnet = getMainnetContractReadOnly(this.client, options) + async getAdminAddress(contractAddress: string): Promise { + const duMainnet = getMainnetContractReadOnly(contractAddress, this.client) return duMainnet.owner() } @@ -856,28 +828,16 @@ export class DataUnionEndpoints { /** * Send a joinRequest, or get into data union instantly with a data union secret - * @param {JoinOptions} options - * - * @typedef {object} JoinOptions - * @property {String} dataUnion Ethereum mainnet address of the data union. If not given, use one given when creating StreamrClient - * @property {String} member Ethereum mainnet address of the joining member. If not given, use StreamrClient authentication key - * @property {String} secret if given, and correct, join the data union immediately */ - async joinDataUnion(options: DataUnionOptions = {}) { - const { - member, - secret, - }: Todo = options - const dataUnion = getMainnetContractReadOnly(this.client, options) - - const body = { - memberAddress: parseAddress(this.client, member) + async join(secret: string|undefined, contractAddress: string): Promise { + const memberAddress = this.client.getAddress() + const body: any = { + memberAddress } - // @ts-expect-error if (secret) { body.secret = secret } - const url = getEndpointUrl(this.client.options.restUrl, 'dataunions', dataUnion.address, 'joinRequests') - return authFetch( + const url = getEndpointUrl(this.client.options.restUrl, 'dataunions', contractAddress, 'joinRequests') + const response = await authFetch( url, this.client.session, { @@ -888,37 +848,31 @@ export class DataUnionEndpoints { }, }, ) + if (secret) { + await until(async () => this.isMember(memberAddress, contractAddress)) + } + return response } - /** - * Await this function when you want to make sure a member is accepted in the data union - * @param {EthereumAddress} memberAddress (optional, default is StreamrClient's auth: privateKey) - * @param {Number} pollingIntervalMs (optional, default: 1000) ask server if member is in - * @param {Number} retryTimeoutMs (optional, default: 60000) give up - * @return {Promise} resolves when member is in the data union (or fails with HTTP error) - */ - async hasJoined(memberAddress: string, options: DataUnionOptions = {}) { - const { - pollingIntervalMs = 1000, - retryTimeoutMs = 60000, - }: Todo = options - const address = parseAddress(this.client, memberAddress) - const duSidechain = await getSidechainContractReadOnly(this.client, options) - - // memberData[0] is enum ActiveStatus {None, Active, Inactive}, and zero means member has never joined - await until(async () => (await duSidechain.memberData(address))[0] !== 0, retryTimeoutMs, pollingIntervalMs) + async isMember(memberAddress: string, contractAddress: string): Promise { + const address = getAddress(memberAddress) + const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) + const ACTIVE = 1 // memberData[0] is enum ActiveStatus {None, Active, Inactive} + const memberData = await duSidechain.memberData(address) + const state = memberData[0] + return (state === ACTIVE) } // TODO: this needs more thought: probably something like getEvents from sidechain? Heavy on RPC? - async getMembers(options: DataUnionOptions) { - const duSidechain = await getSidechainContractReadOnly(this.client, options) + async getMembers(contractAddress: string) { + const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) throw new Error(`Not implemented for side-chain data union (at ${duSidechain.address})`) // event MemberJoined(address indexed); // event MemberParted(address indexed); } - async getDataUnionStats(options: DataUnionOptions) { - const duSidechain = await getSidechainContractReadOnly(this.client, options) + async getStats(contractAddress: string): Promise { + const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) const [ totalEarnings, totalEarningsWithdrawn, @@ -940,14 +894,12 @@ export class DataUnionEndpoints { /** * Get stats of a single data union member - * @param {EthereumAddress} dataUnion to query - * @param {EthereumAddress} memberAddress (optional) if not supplied, get the stats of currently logged in StreamrClient (if auth: privateKey) */ - async getMemberStats(memberAddress: string, options: DataUnionOptions) { - const address = parseAddress(this.client, memberAddress) + async getMemberStats(memberAddress: string, contractAddress: string): Promise { + const address = getAddress(memberAddress) // TODO: use duSidechain.getMemberStats(address) once it's implemented, to ensure atomic read // (so that memberData is from same block as getEarnings, otherwise withdrawable will be foobar) - const duSidechain = await getSidechainContractReadOnly(this.client, options) + const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) const mdata = await duSidechain.memberData(address) const total = await duSidechain.getEarnings(address).catch(() => 0) const withdrawnEarnings = mdata[3].toString() @@ -963,32 +915,20 @@ export class DataUnionEndpoints { /** * Get the amount of tokens the member would get from a successful withdraw - * @param dataUnion to query - * @param memberAddress whose balance is returned - * @return {Promise} */ - async getMemberBalance(memberAddress: string, options: DataUnionOptions) { - const address = parseAddress(this.client, memberAddress) - const duSidechain = await getSidechainContractReadOnly(this.client, options) + async getWithdrawableEarnings(memberAddress: string, contractAddress: string): Promise { + const address = getAddress(memberAddress) + const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) return duSidechain.getWithdrawableEarnings(address) } /** - * Get token balance for given address - * @param {EthereumAddress} address - * @param options such as tokenAddress. If not given, then first check if - * dataUnion was given in StreamrClient constructor, then check if tokenAddress - * was given in StreamrClient constructor. - * @returns {Promise} token balance in "wei" (10^-18 parts) + * Get token balance in "wei" (10^-18 parts) for given address */ - async getTokenBalance(address: string|null|undefined, options: DataUnionOptions) { - const a = parseAddress(this.client, address) - const tokenAddressMainnet = options.tokenAddress || ( - await getMainnetContractReadOnly(this.client, options).then((c: Todo) => c.token()).catch(() => null) || this.client.options.tokenAddress - ) - if (!tokenAddressMainnet) { throw new Error('tokenAddress option not found') } + async getTokenBalance(address: string): Promise { + const a = getAddress(address) const provider = this.client.ethereum.getMainnetProvider() - const token = new Contract(tokenAddressMainnet, [{ + const token = new Contract(this.client.options.tokenAddress, [{ name: 'balanceOf', inputs: [{ type: 'address' }], outputs: [{ type: 'uint256' }], @@ -1003,10 +943,8 @@ export class DataUnionEndpoints { /** * Figure out if given mainnet address is old DataUnion (v 1.0) or current 2.0 * NOTE: Current version of streamr-client-javascript can only handle current version! - * @param {EthereumAddress} contractAddress - * @returns {number} 1 for old, 2 for current, zero for "not a data union" */ - async getDataUnionVersion(contractAddress: string) { + async getVersion(contractAddress: string): Promise { const a = getAddress(contractAddress) // throws if bad address const provider = this.client.ethereum.getMainnetProvider() const du = new Contract(a, [{ @@ -1020,6 +958,7 @@ export class DataUnionEndpoints { const version = await du.version() return +version } catch (e) { + // "not a data union" return 0 } } @@ -1030,29 +969,28 @@ export class DataUnionEndpoints { /** * Withdraw all your earnings - * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) - * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) + * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) */ - async withdraw(options: DataUnionOptions = {}) { + async withdrawAll(contractAddress: string, options?: DataUnionWithdrawOptions): Promise { + const recipientAddress = this.client.getAddress() const tr = await untilWithdrawIsComplete( this.client, - (opts) => this.getWithdrawTx(opts), - (opts) => this.getTokenBalance(null, opts), // null means this StreamrClient's auth credentials - { ...this.client.options, ...options } + () => this.getWithdrawAllTx(contractAddress), + () => this.getTokenBalance(recipientAddress), + options ) return tr } /** * Get the tx promise for withdrawing all your earnings - * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} await on call .wait to actually send the tx */ - async getWithdrawTx(options: DataUnionOptions) { + async getWithdrawAllTx(contractAddress: string): Promise { const signer = await this.client.ethereum.getSidechainSigner() // @ts-expect-error const address = await signer.getAddress() - const duSidechain = await getSidechainContract(this.client, options) + const duSidechain = await getSidechainContract(contractAddress, this.client) const withdrawable = await duSidechain.getWithdrawableEarnings(address) if (withdrawable.eq(0)) { @@ -1068,17 +1006,15 @@ export class DataUnionEndpoints { /** * Withdraw earnings and "donate" them to the given address - * @param {EthereumAddress} recipientAddress the address to receive the tokens - * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) */ - async withdrawTo(recipientAddress: string, options: DataUnionOptions = {}) { + async withdrawAllTo(recipientAddress: string, options: DataUnionWithdrawOptions|undefined, contractAddress: string): Promise { const to = getAddress(recipientAddress) // throws if bad address const tr = await untilWithdrawIsComplete( this.client, - (opts) => this.getWithdrawTxTo(to, opts), - (opts) => this.getTokenBalance(to, opts), - { ...this.client.options, ...options } + () => this.getWithdrawAllToTx(to, contractAddress), + () => this.getTokenBalance(to), + options ) return tr } @@ -1089,11 +1025,11 @@ export class DataUnionEndpoints { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {Promise} await on call .wait to actually send the tx */ - async getWithdrawTxTo(recipientAddress: string, options: DataUnionOptions) { + async getWithdrawAllToTx(recipientAddress: string, contractAddress: string): Promise { const signer = await this.client.ethereum.getSidechainSigner() // @ts-expect-error const address = await signer.getAddress() - const duSidechain = await getSidechainContract(this.client, options) + const duSidechain = await getSidechainContract(contractAddress, this.client) const withdrawable = await duSidechain.getWithdrawableEarnings(address) if (withdrawable.eq(0)) { throw new Error(`${address} has nothing to withdraw in (sidechain) data union ${duSidechain.address}`) @@ -1107,16 +1043,15 @@ export class DataUnionEndpoints { * This signature is only valid until next withdrawal takes place (using this signature or otherwise). * Note that while it's a "blank cheque" for withdrawing all earnings at the moment it's used, it's * invalidated by the first withdraw after signing it. In other words, any signature can be invalidated - * by making a "normal" withdraw e.g. `await streamrClient.withdraw()` + * by making a "normal" withdraw e.g. `await streamrClient.withdrawAll()` * Admin can execute the withdraw using this signature: ``` - * await adminStreamrClient.withdrawToSigned(memberAddress, recipientAddress, signature) + * await adminStreamrClient.withdrawAllToSigned(memberAddress, recipientAddress, signature) * ``` * @param {EthereumAddress} recipientAddress the address authorized to receive the tokens - * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {string} signature authorizing withdrawing all earnings to given recipientAddress */ - async signWithdrawTo(recipientAddress: string, options: DataUnionOptions) { - return this.signWithdrawAmountTo(recipientAddress, BigNumber.from(0), options) + async signWithdrawAllTo(recipientAddress: string, contractAddress: string): Promise { + return this.signWithdrawAmountTo(recipientAddress, BigNumber.from(0), contractAddress) } /** @@ -1128,12 +1063,12 @@ export class DataUnionEndpoints { * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) * @returns {string} signature authorizing withdrawing all earnings to given recipientAddress */ - async signWithdrawAmountTo(recipientAddress: string, amountTokenWei: BigNumber|number|string, options: DataUnionOptions) { + async signWithdrawAmountTo(recipientAddress: string, amountTokenWei: BigNumber|number|string, contractAddress: string): Promise { const to = getAddress(recipientAddress) // throws if bad address const signer = this.client.ethereum.getSigner() // it shouldn't matter if it's mainnet or sidechain signer since key should be the same // @ts-expect-error const address = await signer.getAddress() - const duSidechain = await getSidechainContractReadOnly(this.client, options) + const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) const memberData = await duSidechain.memberData(address) if (memberData[0] === '0') { throw new Error(`${address} is not a member in Data Union (sidechain address ${duSidechain.address})`) } const withdrawn = memberData[3] diff --git a/test/integration/DataUnionEndpoints/adminWithdrawMember.test.js b/test/integration/DataUnionEndpoints/adminWithdrawMember.test.js deleted file mode 100644 index f220d6f04..000000000 --- a/test/integration/DataUnionEndpoints/adminWithdrawMember.test.js +++ /dev/null @@ -1,138 +0,0 @@ -import { Contract, providers, Wallet } from 'ethers' -import { formatEther, parseEther } from 'ethers/lib/utils' -import debug from 'debug' - -import { getEndpointUrl, until } from '../../../src/utils' -import StreamrClient from '../../../src/StreamrClient' -import * as Token from '../../../contracts/TestToken.json' -import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' -import config from '../config' -import authFetch from '../../../src/rest/authFetch' - -const log = debug('StreamrClient::DataUnionEndpoints::integration-test-withdrawTo') -// const { log } = console - -const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) -const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) -const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) -const adminWalletSidechain = new Wallet(config.clientOptions.auth.privateKey, providerSidechain) - -const tokenAdminWallet = new Wallet(config.tokenAdminPrivateKey, providerMainnet) -const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) - -it('DataUnionEndPoints test withdraw from admin', async () => { - log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) - const network = await providerMainnet.getNetwork() - log('Connected to "mainnet" network: ', JSON.stringify(network)) - const network2 = await providerSidechain.getNetwork() - log('Connected to sidechain network: ', JSON.stringify(network2)) - - log(`Minting 100 tokens to ${adminWalletMainnet.address}`) - const tx1 = await tokenMainnet.mint(adminWalletMainnet.address, parseEther('100')) - await tx1.wait() - - const adminClient = new StreamrClient(config.clientOptions) - await adminClient.ensureConnected() - - const dataUnion = await adminClient.deployDataUnion() - const secret = await adminClient.createSecret(dataUnion.address, 'DataUnionEndpoints test secret') - log(`DataUnion ${dataUnion.address} is ready to roll`) - // dataUnion = await adminClient.getDataUnionContract({dataUnion: "0xd778CfA9BB1d5F36E42526B2BAFD07B74b4066c0"}) - - const memberWallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) - const sendTx = await adminWalletSidechain.sendTransaction({ to: memberWallet.address, value: parseEther('0.1') }) - await sendTx.wait() - log(`sent 0.1sETH to ${memberWallet.address}`) - - const memberClient = new StreamrClient({ - ...config.clientOptions, - auth: { - privateKey: memberWallet.privateKey - }, - dataUnion: dataUnion.address, - }) - await memberClient.ensureConnected() - - // product is needed for join requests to analyze the DU version - const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') - await authFetch(createProductUrl, adminClient.session, { - method: 'POST', - body: JSON.stringify({ - beneficiaryAddress: dataUnion.address, - type: 'DATAUNION', - dataUnionVersion: 2 - }) - }) - const res = await memberClient.joinDataUnion({ secret }) - // await adminClient.addMembers([memberWallet.address], { dataUnion }) - log(`Member joined data union: ${JSON.stringify(res)}`) - - const tokenAddress = await dataUnion.token() - log(`Token address: ${tokenAddress}`) - const adminTokenMainnet = new Contract(tokenAddress, Token.abi, adminWalletMainnet) - - const amount = parseEther('1') - const duSidechainEarningsBefore = await dataUnion.sidechain.totalEarnings() - - const duBalance1 = await adminTokenMainnet.balanceOf(dataUnion.address) - log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance1)} (${duBalance1.toString()})`) - const balance1 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) - log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance1)} (${balance1.toString()})`) - - log(`Transferring ${amount} token-wei ${adminWalletMainnet.address}->${dataUnion.address}`) - const txTokenToDU = await adminTokenMainnet.transfer(dataUnion.address, amount) - await txTokenToDU.wait() - - const duBalance2 = await adminTokenMainnet.balanceOf(dataUnion.address) - log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance2)} (${duBalance2.toString()})`) - const balance2 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) - log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance2)} (${balance2.toString()})`) - - log(`DU member count: ${await dataUnion.sidechain.activeMemberCount()}`) - - log(`Transferred ${formatEther(amount)} tokens, next sending to bridge`) - const tx2 = await dataUnion.sendTokensToBridge() - await tx2.wait() - - log(`Sent to bridge, waiting for the tokens to appear at ${dataUnion.sidechain.address} in sidechain`) - const tokenSidechain = new Contract(config.clientOptions.tokenAddressSidechain, Token.abi, adminWalletSidechain) - await until(async () => !(await tokenSidechain.balanceOf(dataUnion.sidechain.address)).eq('0'), 300000, 3000) - log(`Confirmed tokens arrived, DU balance: ${duSidechainEarningsBefore} -> ${await dataUnion.sidechain.totalEarnings()}`) - - const sidechainContract = new Contract(dataUnion.sidechain.address, DataUnionSidechain.abi, adminWalletSidechain) - const tx3 = await sidechainContract.refreshRevenue() - const tr3 = await tx3.wait() - log(`refreshRevenue returned ${JSON.stringify(tr3)}`) - log(`DU balance: ${await dataUnion.sidechain.totalEarnings()}`) - - const duBalance3 = await adminTokenMainnet.balanceOf(dataUnion.address) - log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance3)} (${duBalance3.toString()})`) - const balance3 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) - log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance3)} (${balance3.toString()})`) - - // note: getMemberStats without explicit address => get stats of the authenticated StreamrClient - const stats = await memberClient.getMemberStats() - log(`stats ${JSON.stringify(stats)}`) - - const balanceBefore = await adminTokenMainnet.balanceOf(memberWallet.address) - log(`balanceBefore ${balanceBefore}. Withdrawing tokens...`) - const withdrawTr = await adminClient.withdrawMember(memberWallet.address, { dataUnion }) - log(`Tokens withdrawn, sidechain tx receipt: ${JSON.stringify(withdrawTr)}`) - const balanceAfter = await adminTokenMainnet.balanceOf(memberWallet.address) - const balanceIncrease = balanceAfter.sub(balanceBefore) - - await providerMainnet.removeAllListeners() - await providerSidechain.removeAllListeners() - await memberClient.ensureDisconnected() - await adminClient.ensureDisconnected() - - expect(stats).toMatchObject({ - status: 'active', - earningsBeforeLastJoin: '0', - lmeAtJoin: '0', - totalEarnings: '1000000000000000000', - withdrawableEarnings: '1000000000000000000', - }) - expect(withdrawTr.logs[0].address).toBe(config.clientOptions.tokenAddressSidechain) - expect(balanceIncrease.toString()).toBe(amount.toString()) -}, 300000) diff --git a/test/integration/DataUnionEndpoints/adminWithdrawSigned.test.js b/test/integration/DataUnionEndpoints/adminWithdrawSigned.test.js deleted file mode 100644 index dcb58630d..000000000 --- a/test/integration/DataUnionEndpoints/adminWithdrawSigned.test.js +++ /dev/null @@ -1,155 +0,0 @@ -import { Contract, providers, Wallet } from 'ethers' -import { formatEther, parseEther } from 'ethers/lib/utils' -import debug from 'debug' - -import { getEndpointUrl, until } from '../../../src/utils' -import StreamrClient from '../../../src/StreamrClient' -import * as Token from '../../../contracts/TestToken.json' -import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' -import config from '../config' -import authFetch from '../../../src/rest/authFetch' - -const log = debug('StreamrClient::DataUnionEndpoints::integration-test-withdraw') -// const { log } = console - -const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) -const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) -const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) -const adminWalletSidechain = new Wallet(config.clientOptions.auth.privateKey, providerSidechain) - -const tokenAdminWallet = new Wallet(config.tokenAdminPrivateKey, providerMainnet) -const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) - -it('DataUnionEndPoints test signed withdraw from admin', async () => { - log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) - const network = await providerMainnet.getNetwork() - log('Connected to "mainnet" network: ', JSON.stringify(network)) - const network2 = await providerSidechain.getNetwork() - log('Connected to sidechain network: ', JSON.stringify(network2)) - - log(`Minting 100 tokens to ${adminWalletMainnet.address}`) - const tx1 = await tokenMainnet.mint(adminWalletMainnet.address, parseEther('100')) - await tx1.wait() - - const adminClient = new StreamrClient(config.clientOptions) - await adminClient.ensureConnected() - - const dataUnion = await adminClient.deployDataUnion() - const secret = await adminClient.createSecret(dataUnion.address, 'DataUnionEndpoints test secret') - log(`DataUnion ${dataUnion.address} is ready to roll`) - // dataUnion = await adminClient.getDataUnionContract({dataUnion: "0xd778CfA9BB1d5F36E42526B2BAFD07B74b4066c0"}) - - const memberWallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) - const member2Wallet = new Wallet(`0x100000000000000000000000000000000000000012300000002${Date.now()}`, providerSidechain) - const sendTx = await adminWalletSidechain.sendTransaction({ to: memberWallet.address, value: parseEther('0.1') }) - await sendTx.wait() - log(`sent 0.1sETH to ${memberWallet.address}`) - - const memberClient = new StreamrClient({ - ...config.clientOptions, - auth: { - privateKey: memberWallet.privateKey - }, - dataUnion: dataUnion.address, - }) - await memberClient.ensureConnected() - - // product is needed for join requests to analyze the DU version - const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') - await authFetch(createProductUrl, adminClient.session, { - method: 'POST', - body: JSON.stringify({ - beneficiaryAddress: dataUnion.address, - type: 'DATAUNION', - dataUnionVersion: 2 - }) - }) - await memberClient.joinDataUnion({ secret }) - // await adminClient.addMembers([memberWallet.address], { dataUnion }) - - const tokenAddress = await dataUnion.token() - log(`Token address: ${tokenAddress}`) - const adminTokenMainnet = new Contract(tokenAddress, Token.abi, adminWalletMainnet) - - const amount = parseEther('1') - const duSidechainEarningsBefore = await dataUnion.sidechain.totalEarnings() - - const duBalance1 = await adminTokenMainnet.balanceOf(dataUnion.address) - log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance1)} (${duBalance1.toString()})`) - const balance1 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) - log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance1)} (${balance1.toString()})`) - - log(`Transferring ${amount} token-wei ${adminWalletMainnet.address}->${dataUnion.address}`) - const txTokenToDU = await adminTokenMainnet.transfer(dataUnion.address, amount) - await txTokenToDU.wait() - - const duBalance2 = await adminTokenMainnet.balanceOf(dataUnion.address) - log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance2)} (${duBalance2.toString()})`) - const balance2 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) - log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance2)} (${balance2.toString()})`) - - log(`DU member count: ${await dataUnion.sidechain.activeMemberCount()}`) - - log(`Transferred ${formatEther(amount)} tokens, next sending to bridge`) - const tx2 = await dataUnion.sendTokensToBridge() - await tx2.wait() - - log(`Sent to bridge, waiting for the tokens to appear at ${dataUnion.sidechain.address} in sidechain`) - const tokenSidechain = new Contract(config.clientOptions.tokenAddressSidechain, Token.abi, adminWalletSidechain) - await until(async () => !(await tokenSidechain.balanceOf(dataUnion.sidechain.address)).eq('0'), 300000, 3000) - log(`Confirmed tokens arrived, DU balance: ${duSidechainEarningsBefore} -> ${await dataUnion.sidechain.totalEarnings()}`) - - // make a "full" sidechain contract object that has all functions, not just those required by StreamrClient - const sidechainContract = new Contract(dataUnion.sidechain.address, DataUnionSidechain.abi, adminWalletSidechain) - const tx3 = await sidechainContract.refreshRevenue() - const tr3 = await tx3.wait() - log(`refreshRevenue returned ${JSON.stringify(tr3)}`) - log(`DU balance: ${await dataUnion.sidechain.totalEarnings()}`) - - const duBalance3 = await adminTokenMainnet.balanceOf(dataUnion.address) - log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance3)} (${duBalance3.toString()})`) - const balance3 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) - log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance3)} (${balance3.toString()})`) - - // note: getMemberStats without explicit address => get stats of the authenticated StreamrClient - const stats = await memberClient.getMemberStats() - log(`Stats: ${JSON.stringify(stats)}. Withdrawing tokens...`) - - // try different ways of signing, for coverage; TODO: separate into own test - const signature = await memberClient.signWithdrawTo(member2Wallet.address) - const signature2 = await memberClient.signWithdrawAmountTo(member2Wallet.address, parseEther('1')) - const signature3 = await memberClient.signWithdrawAmountTo(member2Wallet.address, 3000000000000000) // 0.003 tokens - - const isValid = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, '0', signature) // '0' = all earnings - const isValid2 = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, parseEther('1'), signature2) - const isValid3 = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, '3000000000000000', signature3) - log(`Signature for all tokens ${memberWallet.address} -> ${member2Wallet.address}: ${signature}, checked ${isValid ? 'OK' : '!!!BROKEN!!!'}`) - log(`Signature for 1 token ${memberWallet.address} -> ${member2Wallet.address}: ${signature2}, checked ${isValid2 ? 'OK' : '!!!BROKEN!!!'}`) - log(`Signature for 0.003 tokens ${memberWallet.address} -> ${member2Wallet.address}: ${signature3}, checked ${isValid3 ? 'OK' : '!!!BROKEN!!!'}`) - log(`sidechainDU(${sidechainContract.address}) token bal ${await tokenSidechain.balanceOf(sidechainContract.address)}`) - - const balanceBefore = await adminTokenMainnet.balanceOf(member2Wallet.address) - log(`balanceBefore ${balanceBefore}. Withdrawing tokens...`) - const withdrawTr = await adminClient.withdrawToSigned(memberWallet.address, member2Wallet.address, signature, { dataUnion }) - log(`Tokens withdrawn, sidechain tx receipt: ${JSON.stringify(withdrawTr)}`) - const balanceAfter = await adminTokenMainnet.balanceOf(member2Wallet.address) - const balanceIncrease = balanceAfter.sub(balanceBefore) - - await providerMainnet.removeAllListeners() - await providerSidechain.removeAllListeners() - await memberClient.ensureDisconnected() - await adminClient.ensureDisconnected() - - expect(stats).toMatchObject({ - status: 'active', - earningsBeforeLastJoin: '0', - lmeAtJoin: '0', - totalEarnings: '1000000000000000000', - withdrawableEarnings: '1000000000000000000', - }) - expect(withdrawTr.logs[0].address).toBe(config.clientOptions.tokenAddressSidechain) - expect(balanceIncrease.toString()).toBe(amount.toString()) - expect(isValid).toBe(true) - expect(isValid2).toBe(true) - expect(isValid3).toBe(true) -}, 300000) diff --git a/test/integration/DataUnionEndpoints/withdraw.test.js b/test/integration/DataUnionEndpoints/withdraw.test.js deleted file mode 100644 index 08d4a0e8d..000000000 --- a/test/integration/DataUnionEndpoints/withdraw.test.js +++ /dev/null @@ -1,140 +0,0 @@ -import { Contract, providers, Wallet } from 'ethers' -import { formatEther, parseEther } from 'ethers/lib/utils' -import debug from 'debug' - -import { getEndpointUrl, until } from '../../../src/utils' -import StreamrClient from '../../../src/StreamrClient' -import * as Token from '../../../contracts/TestToken.json' -import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' -import config from '../config' -import authFetch from '../../../src/rest/authFetch' - -const log = debug('StreamrClient::DataUnionEndpoints::integration-test-withdraw') -// const { log } = console - -const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) -const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) -const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) -const adminWalletSidechain = new Wallet(config.clientOptions.auth.privateKey, providerSidechain) - -const tokenAdminWallet = new Wallet(config.tokenAdminPrivateKey, providerMainnet) -const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) - -it('DataUnionEndPoints test withdraw by member itself', async () => { - log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) - const network = await providerMainnet.getNetwork() - log('Connected to "mainnet" network: ', JSON.stringify(network)) - const network2 = await providerSidechain.getNetwork() - log('Connected to sidechain network: ', JSON.stringify(network2)) - - log(`Minting 100 tokens to ${adminWalletMainnet.address}`) - const tx1 = await tokenMainnet.mint(adminWalletMainnet.address, parseEther('100')) - await tx1.wait() - - const adminClient = new StreamrClient(config.clientOptions) - await adminClient.ensureConnected() - - const dataUnion = await adminClient.deployDataUnion() - const secret = await adminClient.createSecret(dataUnion.address, 'DataUnionEndpoints test secret') - log(`DataUnion ${dataUnion.address} is ready to roll`) - // dataUnion = await adminClient.getDataUnionContract({dataUnion: "0xd778CfA9BB1d5F36E42526B2BAFD07B74b4066c0"}) - - const memberWallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) - const sendTx = await adminWalletSidechain.sendTransaction({ to: memberWallet.address, value: parseEther('0.1') }) - await sendTx.wait() - log(`Sent 0.1 sidechain-ETH to ${memberWallet.address}`) - - const send2Tx = await adminWalletMainnet.sendTransaction({ to: memberWallet.address, value: parseEther('0.1') }) - await send2Tx.wait() - log(`Sent 0.1 mainnet-ETH to ${memberWallet.address}`) - - const memberClient = new StreamrClient({ - ...config.clientOptions, - auth: { - privateKey: memberWallet.privateKey - }, - dataUnion: dataUnion.address, - }) - await memberClient.ensureConnected() - - // product is needed for join requests to analyze the DU version - const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') - await authFetch(createProductUrl, adminClient.session, { - method: 'POST', - body: JSON.stringify({ - beneficiaryAddress: dataUnion.address, - type: 'DATAUNION', - dataUnionVersion: 2 - }) - }) - await memberClient.joinDataUnion({ secret }) - // await adminClient.addMembers([memberWallet.address], { dataUnion }) - - const tokenAddress = await dataUnion.token() - log(`Token address: ${tokenAddress}`) - const adminTokenMainnet = new Contract(tokenAddress, Token.abi, adminWalletMainnet) - - const amount = parseEther('1') - const duSidechainEarningsBefore = await dataUnion.sidechain.totalEarnings() - - const duBalance1 = await adminTokenMainnet.balanceOf(dataUnion.address) - log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance1)} (${duBalance1.toString()})`) - const balance1 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) - log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance1)} (${balance1.toString()})`) - - log(`Transferring ${amount} token-wei ${adminWalletMainnet.address}->${dataUnion.address}`) - const txTokenToDU = await adminTokenMainnet.transfer(dataUnion.address, amount) - await txTokenToDU.wait() - - const duBalance2 = await adminTokenMainnet.balanceOf(dataUnion.address) - log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance2)} (${duBalance2.toString()})`) - const balance2 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) - log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance2)} (${balance2.toString()})`) - - log(`DU member count: ${await dataUnion.sidechain.activeMemberCount()}`) - - log(`Transferred ${formatEther(amount)} tokens, next sending to bridge`) - const tx2 = await dataUnion.sendTokensToBridge() - await tx2.wait() - - log(`Sent to bridge, waiting for the tokens to appear at ${dataUnion.sidechain.address} in sidechain`) - const tokenSidechain = new Contract(config.clientOptions.tokenAddressSidechain, Token.abi, adminWalletSidechain) - await until(async () => !(await tokenSidechain.balanceOf(dataUnion.sidechain.address)).eq('0'), 300000, 3000) - log(`Confirmed tokens arrived, DU balance: ${duSidechainEarningsBefore} -> ${await dataUnion.sidechain.totalEarnings()}`) - - const sidechainContract = new Contract(dataUnion.sidechain.address, DataUnionSidechain.abi, adminWalletSidechain) - const tx3 = await sidechainContract.refreshRevenue() - const tr3 = await tx3.wait() - log(`refreshRevenue returned ${JSON.stringify(tr3)}`) - log(`DU balance: ${await dataUnion.sidechain.totalEarnings()}`) - - const duBalance3 = await adminTokenMainnet.balanceOf(dataUnion.address) - log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance3)} (${duBalance3.toString()})`) - const balance3 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) - log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance3)} (${balance3.toString()})`) - - // note: getMemberStats without explicit address => get stats of the authenticated StreamrClient - const stats = await memberClient.getMemberStats() - log(`Stats: ${JSON.stringify(stats)}. Withdrawing tokens...`) - - const balanceBefore = await adminTokenMainnet.balanceOf(memberWallet.address) - const withdrawTr = await memberClient.withdraw() - log(`Tokens withdrawn, sidechain tx receipt: ${JSON.stringify(withdrawTr)}`) - const balanceAfter = await adminTokenMainnet.balanceOf(memberWallet.address) - const balanceIncrease = balanceAfter.sub(balanceBefore) - - await providerMainnet.removeAllListeners() - await providerSidechain.removeAllListeners() - await memberClient.ensureDisconnected() - await adminClient.ensureDisconnected() - - expect(stats).toMatchObject({ - status: 'active', - earningsBeforeLastJoin: '0', - lmeAtJoin: '0', - totalEarnings: '1000000000000000000', - withdrawableEarnings: '1000000000000000000', - }) - expect(withdrawTr.logs[0].address).toBe(config.clientOptions.tokenAddressSidechain) - expect(balanceIncrease.toString()).toBe(amount.toString()) -}, 300000) diff --git a/test/integration/DataUnionEndpoints/withdrawTo.test.js b/test/integration/DataUnionEndpoints/withdrawTo.test.js deleted file mode 100644 index b7624c9e0..000000000 --- a/test/integration/DataUnionEndpoints/withdrawTo.test.js +++ /dev/null @@ -1,142 +0,0 @@ -import { Contract, providers, Wallet } from 'ethers' -import { formatEther, parseEther } from 'ethers/lib/utils' -import debug from 'debug' - -import { getEndpointUrl, until } from '../../../src/utils' -import StreamrClient from '../../../src/StreamrClient' -import * as Token from '../../../contracts/TestToken.json' -import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' -import config from '../config' -import authFetch from '../../../src/rest/authFetch' - -const log = debug('StreamrClient::DataUnionEndpoints::integration-test-withdrawTo') -// const { log } = console - -const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) -const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) -const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) -const adminWalletSidechain = new Wallet(config.clientOptions.auth.privateKey, providerSidechain) - -const tokenAdminWallet = new Wallet(config.tokenAdminPrivateKey, providerMainnet) -const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) - -it('DataUnionEndPoints test withdrawTo from member to any address', async () => { - log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) - const network = await providerMainnet.getNetwork() - log('Connected to "mainnet" network: ', JSON.stringify(network)) - const network2 = await providerSidechain.getNetwork() - log('Connected to sidechain network: ', JSON.stringify(network2)) - - log(`Minting 100 tokens to ${adminWalletMainnet.address}`) - const tx1 = await tokenMainnet.mint(adminWalletMainnet.address, parseEther('100')) - await tx1.wait() - - const adminClient = new StreamrClient(config.clientOptions) - await adminClient.ensureConnected() - - const dataUnion = await adminClient.deployDataUnion() - const secret = await adminClient.createSecret(dataUnion.address, 'DataUnionEndpoints test secret') - log(`DataUnion ${dataUnion.address} is ready to roll`) - // dataUnion = await adminClient.getDataUnionContract({dataUnion: "0xd778CfA9BB1d5F36E42526B2BAFD07B74b4066c0"}) - - const memberWallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) - const outsiderWallet = new Wallet(`0x100000000000000000000000000000000000000012300000002${Date.now()}`, providerSidechain) - const sendTx = await adminWalletSidechain.sendTransaction({ to: memberWallet.address, value: parseEther('0.1') }) - await sendTx.wait() - log(`Sent 0.1 sidechain-ETH to ${memberWallet.address}`) - - const send2Tx = await adminWalletMainnet.sendTransaction({ to: memberWallet.address, value: parseEther('0.1') }) - await send2Tx.wait() - log(`Sent 0.1 mainnet-ETH to ${memberWallet.address}`) - - const memberClient = new StreamrClient({ - ...config.clientOptions, - auth: { - privateKey: memberWallet.privateKey - }, - dataUnion: dataUnion.address, - }) - await memberClient.ensureConnected() - - // product is needed for join requests to analyze the DU version - const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') - await authFetch(createProductUrl, adminClient.session, { - method: 'POST', - body: JSON.stringify({ - beneficiaryAddress: dataUnion.address, - type: 'DATAUNION', - dataUnionVersion: 2 - }) - }) - await memberClient.joinDataUnion({ secret }) - // await adminClient.addMembers([memberWallet.address], { dataUnion }) - - const tokenAddress = await dataUnion.token() - log(`Token address: ${tokenAddress}`) - const adminTokenMainnet = new Contract(tokenAddress, Token.abi, adminWalletMainnet) - - const amount = parseEther('1') - const duSidechainEarningsBefore = await dataUnion.sidechain.totalEarnings() - - const duBalance1 = await adminTokenMainnet.balanceOf(dataUnion.address) - log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance1)} (${duBalance1.toString()})`) - const balance1 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) - log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance1)} (${balance1.toString()})`) - - log(`Transferring ${amount} token-wei ${adminWalletMainnet.address}->${dataUnion.address}`) - const txTokenToDU = await adminTokenMainnet.transfer(dataUnion.address, amount) - await txTokenToDU.wait() - - const duBalance2 = await adminTokenMainnet.balanceOf(dataUnion.address) - log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance2)} (${duBalance2.toString()})`) - const balance2 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) - log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance2)} (${balance2.toString()})`) - - log(`DU member count: ${await dataUnion.sidechain.activeMemberCount()}`) - - log(`Transferred ${formatEther(amount)} tokens, next sending to bridge`) - const tx2 = await dataUnion.sendTokensToBridge() - await tx2.wait() - - log(`Sent to bridge, waiting for the tokens to appear at ${dataUnion.sidechain.address} in sidechain`) - const tokenSidechain = new Contract(config.clientOptions.tokenAddressSidechain, Token.abi, adminWalletSidechain) - await until(async () => !(await tokenSidechain.balanceOf(dataUnion.sidechain.address)).eq('0'), 300000, 3000) - log(`Confirmed tokens arrived, DU balance: ${duSidechainEarningsBefore} -> ${await dataUnion.sidechain.totalEarnings()}`) - - const sidechainContract = new Contract(dataUnion.sidechain.address, DataUnionSidechain.abi, adminWalletSidechain) - const tx3 = await sidechainContract.refreshRevenue() - const tr3 = await tx3.wait() - log(`refreshRevenue returned ${JSON.stringify(tr3)}`) - log(`DU balance: ${await dataUnion.sidechain.totalEarnings()}`) - - const duBalance3 = await adminTokenMainnet.balanceOf(dataUnion.address) - log(`Token balance of ${dataUnion.address}: ${formatEther(duBalance3)} (${duBalance3.toString()})`) - const balance3 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) - log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance3)} (${balance3.toString()})`) - - // note: getMemberStats without explicit address => get stats of the authenticated StreamrClient - const stats = await memberClient.getMemberStats() - log(`stats ${JSON.stringify(stats)}`) - - const balanceBefore = await adminTokenMainnet.balanceOf(outsiderWallet.address) - log(`balanceBefore ${balanceBefore}. Withdrawing tokens...`) - const withdrawTr = await memberClient.withdrawTo(outsiderWallet.address) - log(`Tokens withdrawn, sidechain tx receipt: ${JSON.stringify(withdrawTr)}`) - const balanceAfter = await adminTokenMainnet.balanceOf(outsiderWallet.address) - const balanceIncrease = balanceAfter.sub(balanceBefore) - - await providerMainnet.removeAllListeners() - await providerSidechain.removeAllListeners() - await memberClient.ensureDisconnected() - await adminClient.ensureDisconnected() - - expect(stats).toMatchObject({ - status: 'active', - earningsBeforeLastJoin: '0', - lmeAtJoin: '0', - totalEarnings: '1000000000000000000', - withdrawableEarnings: '1000000000000000000', - }) - expect(withdrawTr.logs[0].address).toBe(config.clientOptions.tokenAddressSidechain) - expect(balanceIncrease.toString()).toBe(amount.toString()) -}, 300000) diff --git a/test/integration/config.js b/test/integration/config.js index 4c6328d5c..f103ad654 100644 --- a/test/integration/config.js +++ b/test/integration/config.js @@ -10,6 +10,7 @@ module.exports = { tokenAddress: process.env.TOKEN_ADDRESS || '0xbAA81A0179015bE47Ad439566374F2Bae098686F', tokenAddressSidechain: process.env.TOKEN_ADDRESS_SIDECHAIN || '0x73Be21733CC5D08e1a14Ea9a399fb27DB3BEf8fF', factoryMainnetAddress: process.env.DU_FACTORY_MAINNET || '0x5E959e5d5F3813bE5c6CeA996a286F734cc9593b', + factorySidechainAddress: process.env.DU_FACTORY_SIDECHAIN || '0x4081B7e107E59af8E82756F96C751174590989FE', sidechain: { url: process.env.SIDECHAIN_URL || 'http://10.200.10.1:8546', timeout: process.env.TEST_TIMEOUT, diff --git a/test/integration/DataUnionEndpoints/DataUnionEndpoints.test.js b/test/integration/dataunion/DataUnionEndpoints.test.ts similarity index 79% rename from test/integration/DataUnionEndpoints/DataUnionEndpoints.test.js rename to test/integration/dataunion/DataUnionEndpoints.test.ts index 0a5ab0a21..8182d4a5d 100644 --- a/test/integration/DataUnionEndpoints/DataUnionEndpoints.test.js +++ b/test/integration/dataunion/DataUnionEndpoints.test.ts @@ -8,16 +8,19 @@ import authFetch from '../../../src/rest/authFetch' import StreamrClient from '../../../src/StreamrClient' import * as Token from '../../../contracts/TestToken.json' import config from '../config' +import { DataUnion } from '../../../src/dataunion/DataUnion' const log = debug('StreamrClient::DataUnionEndpoints::integration-test') // const log = console.log +// @ts-expect-error const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +// @ts-expect-error const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) describe('DataUnionEndPoints', () => { - let adminClient + let adminClient: StreamrClient const tokenAdminWallet = new Wallet(config.tokenAdminPrivateKey, providerMainnet) const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) @@ -42,19 +45,19 @@ describe('DataUnionEndPoints', () => { const tx1 = await tokenMainnet.mint(adminWalletMainnet.address, parseEther('100')) await tx1.wait() - adminClient = new StreamrClient(config.clientOptions) + adminClient = new StreamrClient(config.clientOptions as any) await adminClient.ensureConnected() }, 10000) // fresh dataUnion for each test case, created NOT in parallel to avoid nonce troubles const adminMutex = new Mutex() async function deployDataUnionSync(testName) { - let dataUnion + let dataUnion: DataUnion await adminMutex.runExclusive(async () => { const dataUnionName = testName + Date.now() log(`Starting deployment of dataUnionName=${dataUnionName}`) dataUnion = await adminClient.deployDataUnion({ dataUnionName }) - log(`DataUnion ${dataUnion.address} is ready to roll`) + log(`DataUnion ${dataUnion.getAddress()} is ready to roll`) // product is needed for join requests to analyze the DU version const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') @@ -64,7 +67,7 @@ describe('DataUnionEndPoints', () => { { method: 'POST', body: JSON.stringify({ - beneficiaryAddress: dataUnion.address, + beneficiaryAddress: dataUnion.getAddress(), type: 'DATAUNION', dataUnionVersion: 2 }) @@ -83,11 +86,8 @@ describe('DataUnionEndPoints', () => { it('can add members', async () => { const dataUnion = await deployDataUnionSync('add-members-test') - await adminMutex.runExclusive(async () => { - await adminClient.addMembers(memberAddressList, { dataUnion }) - await adminClient.hasJoined(memberAddressList[0], { dataUnion }) - }) - const res = await adminClient.getDataUnionStats({ dataUnion }) + await adminMutex.runExclusive(() => dataUnion.addMembers(memberAddressList)) + const res = await dataUnion.getStats() expect(+res.activeMemberCount).toEqual(3) expect(+res.inactiveMemberCount).toEqual(0) }, 150000) @@ -95,24 +95,24 @@ describe('DataUnionEndPoints', () => { it('can remove members', async () => { const dataUnion = await deployDataUnionSync('remove-members-test') await adminMutex.runExclusive(async () => { - await adminClient.addMembers(memberAddressList, { dataUnion }) - await adminClient.kick(memberAddressList.slice(1), { dataUnion }) + await dataUnion.addMembers(memberAddressList) + await dataUnion.removeMembers(memberAddressList.slice(1)) }) - const res = await adminClient.getDataUnionStats({ dataUnion }) + const res = await dataUnion.getStats() expect(+res.activeMemberCount).toEqual(1) expect(+res.inactiveMemberCount).toEqual(2) }, 150000) it('can set admin fee', async () => { const dataUnion = await deployDataUnionSync('set-admin-fee-test') - const oldFee = await adminClient.getAdminFee({ dataUnion }) + const oldFee = await dataUnion.getAdminFee() await adminMutex.runExclusive(async () => { - log(`DU owner: ${await adminClient.getAdminAddress({ dataUnion })}`) + log(`DU owner: ${await dataUnion.getAdminAddress()}`) log(`Sending tx from ${adminClient.getAddress()}`) - const tr = await adminClient.setAdminFee(0.1, { dataUnion }) + const tr = await dataUnion.setAdminFee(0.1) log(`Transaction receipt: ${JSON.stringify(tr)}`) }) - const newFee = await adminClient.getAdminFee({ dataUnion }) + const newFee = await dataUnion.getAdminFee() expect(oldFee).toEqual(0) expect(newFee).toEqual(0.1) }, 150000) @@ -121,18 +121,20 @@ describe('DataUnionEndPoints', () => { const dataUnion = await deployDataUnionSync('withdraw-admin-fees-test') await adminMutex.runExclusive(async () => { - await adminClient.addMembers(memberAddressList, { dataUnion }) - const tr = await adminClient.setAdminFee(0.1, { dataUnion }) + await dataUnion.addMembers(memberAddressList) + const tr = await dataUnion.setAdminFee(0.1) log(`Transaction receipt: ${JSON.stringify(tr)}`) }) const amount = parseEther('2') - const tokenAddress = await dataUnion.token() + // eslint-disable-next-line no-underscore-dangle + const contract = await dataUnion._getContract() + const tokenAddress = await contract.token() const adminTokenMainnet = new Contract(tokenAddress, Token.abi, adminWalletMainnet) await adminMutex.runExclusive(async () => { - log(`Transferring ${amount} token-wei ${adminWalletMainnet.address}->${dataUnion.address}`) - const txTokenToDU = await adminTokenMainnet.transfer(dataUnion.address, amount) + log(`Transferring ${amount} token-wei ${adminWalletMainnet.address}->${dataUnion.getAddress()}`) + const txTokenToDU = await adminTokenMainnet.transfer(dataUnion.getAddress(), amount) await txTokenToDU.wait() }) @@ -140,7 +142,7 @@ describe('DataUnionEndPoints', () => { log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance1)} (${balance1.toString()})`) log(`Transferred ${formatEther(amount)} tokens, next sending to bridge`) - const tx2 = await dataUnion.sendTokensToBridge() + const tx2 = await contract.sendTokensToBridge() await tx2.wait() const balance2 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) @@ -158,16 +160,15 @@ describe('DataUnionEndPoints', () => { `0x300000000000000000000000000${nonce}`, ] - async function getOutsiderClient(dataUnion) { + async function getOutsiderClient() { const client = new StreamrClient({ ...config.clientOptions, auth: { apiKey: 'tester1-api-key' }, - dataUnion: dataUnion.address, autoConnect: false, autoDisconnect: false, - }) + } as any) streamrClientCleanupList.push(client) return client } @@ -175,10 +176,10 @@ describe('DataUnionEndPoints', () => { it('can get dataUnion stats', async () => { const dataUnion = await deployDataUnionSync('get-du-stats-test') await adminMutex.runExclusive(async () => { - await adminClient.addMembers(memberAddressList, { dataUnion }) + await dataUnion.addMembers(memberAddressList) }) - const client = await getOutsiderClient(dataUnion) - const stats = await client.getDataUnionStats() + const client = await getOutsiderClient() + const stats = await client.getDataUnion(dataUnion.getAddress()).getStats() expect(+stats.activeMemberCount).toEqual(3) expect(+stats.inactiveMemberCount).toEqual(0) expect(+stats.joinPartAgentCount).toEqual(2) @@ -190,10 +191,10 @@ describe('DataUnionEndPoints', () => { it('can get member stats', async () => { const dataUnion = await deployDataUnionSync('get-member-stats-test') await adminMutex.runExclusive(async () => { - await adminClient.addMembers(memberAddressList, { dataUnion }) + await dataUnion.addMembers(memberAddressList) }) - const client = await getOutsiderClient(dataUnion) - const memberStats = await Promise.all(memberAddressList.map((m) => client.getMemberStats(m))) + const client = await getOutsiderClient() + const memberStats = await Promise.all(memberAddressList.map((m) => client.getDataUnion(dataUnion.getAddress()).getMemberStats(m))) expect(memberStats).toMatchObject([{ status: 'active', earningsBeforeLastJoin: '0', diff --git a/test/integration/DataUnionEndpoints/calculate.test.js b/test/integration/dataunion/calculate.test.ts similarity index 57% rename from test/integration/DataUnionEndpoints/calculate.test.js rename to test/integration/dataunion/calculate.test.ts index e7d92ca4e..17fc03929 100644 --- a/test/integration/DataUnionEndpoints/calculate.test.js +++ b/test/integration/dataunion/calculate.test.ts @@ -7,10 +7,15 @@ import config from '../config' const log = debug('StreamrClient::DataUnionEndpoints::integration-test-calculate') // const { log } = console +// @ts-expect-error const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +// @ts-expect-error const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) +// This test will fail when new docker images are pushed with updated DU smart contracts +// -> generate new codehashes for getDataUnionMainnetAddress() and getDataUnionSidechainAddress() + it('DataUnionEndPoints: calculate DU address before deployment', async () => { log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) const network = await providerMainnet.getNetwork() @@ -18,22 +23,21 @@ it('DataUnionEndPoints: calculate DU address before deployment', async () => { const network2 = await providerSidechain.getNetwork() log('Connected to sidechain network: ', JSON.stringify(network2)) - const adminClient = new StreamrClient(config.clientOptions) + const adminClient = new StreamrClient(config.clientOptions as any) await adminClient.ensureConnected() - const dataUnionName = '6be8ceda7a3c4fe7991eab501975b85ec2bb90452d0e4c93bc2' + Date.now() - const duMainnetAddress = await adminClient.calculateDataUnionMainnetAddress(dataUnionName, adminWalletMainnet.address) - const duSidechainAddress = await adminClient.calculateDataUnionSidechainAddress(duMainnetAddress) - - const dataUnion = await adminClient.deployDataUnion({ dataUnionName }) + const dataUnionName = 'test-' + Date.now() + // eslint-disable-next-line no-underscore-dangle + const dataUnionPredicted = adminClient._getDataUnionFromName({ dataUnionName, deployerAddress: adminWalletMainnet.address }) - const version = await adminClient.getDataUnionVersion(dataUnion.address) + const dataUnionDeployed = await adminClient.deployDataUnion({ dataUnionName }) + const version = await dataUnionDeployed.getVersion() await providerMainnet.removeAllListeners() await providerSidechain.removeAllListeners() await adminClient.ensureDisconnected() - expect(duMainnetAddress).toBe(dataUnion.address) - expect(duSidechainAddress).toBe(dataUnion.sidechain.address) + expect(dataUnionPredicted.getAddress()).toBe(dataUnionDeployed.getAddress()) + expect(dataUnionPredicted.getSidechainAddress()).toBe(dataUnionDeployed.getSidechainAddress()) expect(version).toBe(2) }, 60000) diff --git a/test/integration/dataunion/deploy.test.ts b/test/integration/dataunion/deploy.test.ts new file mode 100644 index 000000000..943e0a50b --- /dev/null +++ b/test/integration/dataunion/deploy.test.ts @@ -0,0 +1,49 @@ +import { providers } from 'ethers' +import debug from 'debug' + +import StreamrClient from '../../../src/StreamrClient' +import config from '../config' + +const log = debug('StreamrClient::DataUnionEndpoints::integration-test-deploy') + +// @ts-expect-error +const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +// @ts-expect-error +const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) + +const createMockAddress = () => '0x000000000000000000000000000' + Date.now() + +describe('DataUnion deployment', () => { + + let adminClient + + beforeAll(async () => { + log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) + const network = await providerMainnet.getNetwork() + log('Connected to "mainnet" network: ', JSON.stringify(network)) + const network2 = await providerSidechain.getNetwork() + log('Connected to sidechain network: ', JSON.stringify(network2)) + adminClient = new StreamrClient(config.clientOptions as any) + await adminClient.ensureConnected() + }, 60000) + + describe('owner', () => { + + it('not specified: defaults to deployer', async () => { + const dataUnion = await adminClient.deployDataUnion() + expect(await dataUnion.getAdminAddress()).toBe(adminClient.getAddress()) + }, 60000) + + it('specified', async () => { + const owner = createMockAddress() + const dataUnion = await adminClient.deployDataUnion({ owner }) + expect(await dataUnion.getAdminAddress()).toBe(owner) + }, 60000) + + it('invalid', () => { + return expect(() => adminClient.deployDataUnion({ owner: 'foobar' })).rejects.toThrow('invalid address') + }, 60000) + + }) +}) + diff --git a/test/integration/dataunion/member.test.ts b/test/integration/dataunion/member.test.ts new file mode 100644 index 000000000..99d2e2df5 --- /dev/null +++ b/test/integration/dataunion/member.test.ts @@ -0,0 +1,102 @@ +import { providers, Wallet } from 'ethers' +import debug from 'debug' + +import StreamrClient from '../../../src/StreamrClient' +import config from '../config' +import { DataUnion, JoinRequestState } from '../../../src/dataunion/DataUnion' +import { fakePrivateKey } from '../../utils' +import authFetch from '../../../src/rest/authFetch' +import { getEndpointUrl } from '../../../src/utils' + +const log = debug('StreamrClient::DataUnionEndpoints::integration-test-member') + +// @ts-expect-error +const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +// @ts-expect-error +const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) + +const createMockAddress = () => '0x000000000000000000000000000' + Date.now() + +const joinMember = async (memberWallet: Wallet, secret: string|undefined, dataUnionAddress: string) => { + const memberClient = new StreamrClient({ + ...config.clientOptions, + auth: { + privateKey: memberWallet.privateKey, + } + } as any) + await memberClient.ensureConnected() + return await memberClient.getDataUnion(dataUnionAddress).join(secret) +} + +describe('DataUnion member', () => { + + let dataUnion: DataUnion + let secret: string + + beforeAll(async () => { + log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) + const network = await providerMainnet.getNetwork() + log('Connected to "mainnet" network: ', JSON.stringify(network)) + const network2 = await providerSidechain.getNetwork() + log('Connected to sidechain network: ', JSON.stringify(network2)) + const adminClient = new StreamrClient(config.clientOptions as any) + await adminClient.ensureConnected() + dataUnion = await adminClient.deployDataUnion() + // product is needed for join requests to analyze the DU version + const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') + await authFetch( + createProductUrl, + adminClient.session, + { + method: 'POST', + body: JSON.stringify({ + beneficiaryAddress: dataUnion.getAddress(), + type: 'DATAUNION', + dataUnionVersion: 2 + }) + } + ) + secret = await dataUnion.createSecret() + }, 60000) + + it('random user is not a member', async () => { + const userAddress = createMockAddress() + const isMember = await dataUnion.isMember(userAddress) + expect(isMember).toBe(false) + }, 60000) + + it('join with valid secret', async () => { + const memberWallet = new Wallet(fakePrivateKey()) + await joinMember(memberWallet, secret, dataUnion.getAddress()) + const isMember = await dataUnion.isMember(memberWallet.address) + expect(isMember).toBe(true) + }, 60000) + + it('join with invalid secret', async () => { + const memberWallet = new Wallet(fakePrivateKey()) + return expect(() => joinMember(memberWallet, 'invalid-secret', dataUnion.getAddress())).rejects.toThrow('Incorrect data union secret') + }, 60000) + + it('join without secret', async () => { + const memberWallet = new Wallet(fakePrivateKey()) + const response = await joinMember(memberWallet, undefined, dataUnion.getAddress()) + expect(response.id).toBeDefined() + expect(response.state).toBe(JoinRequestState.PENDING) + }, 60000) + + it('add', async () => { + const userAddress = createMockAddress() + await dataUnion.addMembers([userAddress]) + const isMember = await dataUnion.isMember(userAddress) + expect(isMember).toBe(true) + }, 60000) + + it('remove', async () => { + const userAddress = createMockAddress() + await dataUnion.addMembers([userAddress]) + await dataUnion.removeMembers([userAddress]) + const isMember = await dataUnion.isMember(userAddress) + expect(isMember).toBe(false) + }, 60000) + +}) diff --git a/test/integration/dataunion/signature.test.ts b/test/integration/dataunion/signature.test.ts new file mode 100644 index 000000000..8981c29da --- /dev/null +++ b/test/integration/dataunion/signature.test.ts @@ -0,0 +1,70 @@ +import { Contract, providers, Wallet } from 'ethers' +import { parseEther } from 'ethers/lib/utils' +import debug from 'debug' + +import { getEndpointUrl } from '../../../src/utils' +import StreamrClient from '../../../src/StreamrClient' +import * as Token from '../../../contracts/TestToken.json' +import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' +import config from '../config' +import authFetch from '../../../src/rest/authFetch' + +const log = debug('StreamrClient::DataUnionEndpoints::integration-test-signature') + +// @ts-expect-error +const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +const adminWalletSidechain = new Wallet(config.clientOptions.auth.privateKey, providerSidechain) + +it('DataUnion signature', async () => { + + const adminClient = new StreamrClient(config.clientOptions as any) + await adminClient.ensureConnected() + const dataUnion = await adminClient.deployDataUnion() + const secret = await dataUnion.createSecret('DataUnionEndpoints test secret') + log(`DataUnion ${dataUnion.getAddress()} is ready to roll`) + + const memberWallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) + const member2Wallet = new Wallet(`0x100000000000000000000000000000000000000012300000002${Date.now()}`, providerSidechain) + + const memberClient = new StreamrClient({ + ...config.clientOptions, + auth: { + privateKey: memberWallet.privateKey + } + } as any) + await memberClient.ensureConnected() + + // product is needed for join requests to analyze the DU version + const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') + await authFetch(createProductUrl, adminClient.session, { + method: 'POST', + body: JSON.stringify({ + beneficiaryAddress: dataUnion.getAddress(), + type: 'DATAUNION', + dataUnionVersion: 2 + }) + }) + await memberClient.getDataUnion(dataUnion.getAddress()).join(secret) + + // eslint-disable-next-line no-underscore-dangle + const contract = await dataUnion._getContract() + const sidechainContract = new Contract(contract.sidechain.address, DataUnionSidechain.abi, adminWalletSidechain) + const tokenSidechain = new Contract(config.clientOptions.tokenAddressSidechain, Token.abi, adminWalletSidechain) + + const signature = await memberClient.getDataUnion(dataUnion.getAddress()).signWithdrawAllTo(member2Wallet.address) + const signature2 = await memberClient.getDataUnion(dataUnion.getAddress()).signWithdrawAmountTo(member2Wallet.address, parseEther('1')) + const signature3 = await memberClient.getDataUnion(dataUnion.getAddress()).signWithdrawAmountTo(member2Wallet.address, 3000000000000000) // 0.003 tokens + + const isValid = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, '0', signature) // '0' = all earnings + const isValid2 = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, parseEther('1'), signature2) + const isValid3 = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, '3000000000000000', signature3) + log(`Signature for all tokens ${memberWallet.address} -> ${member2Wallet.address}: ${signature}, checked ${isValid ? 'OK' : '!!!BROKEN!!!'}`) + log(`Signature for 1 token ${memberWallet.address} -> ${member2Wallet.address}: ${signature2}, checked ${isValid2 ? 'OK' : '!!!BROKEN!!!'}`) + log(`Signature for 0.003 tokens ${memberWallet.address} -> ${member2Wallet.address}: ${signature3}, checked ${isValid3 ? 'OK' : '!!!BROKEN!!!'}`) + log(`sidechainDU(${sidechainContract.address}) token bal ${await tokenSidechain.balanceOf(sidechainContract.address)}`) + + expect(isValid).toBe(true) + expect(isValid2).toBe(true) + expect(isValid3).toBe(true) + +}, 100000) diff --git a/test/integration/dataunion/withdraw.test.ts b/test/integration/dataunion/withdraw.test.ts new file mode 100644 index 000000000..99740b07b --- /dev/null +++ b/test/integration/dataunion/withdraw.test.ts @@ -0,0 +1,198 @@ +import { BigNumber, Contract, providers, Wallet } from 'ethers' +import { formatEther, parseEther } from 'ethers/lib/utils' +import { TransactionReceipt } from '@ethersproject/providers' +import debug from 'debug' + +import { getEndpointUrl, until } from '../../../src/utils' +import StreamrClient from '../../../src/StreamrClient' +import * as Token from '../../../contracts/TestToken.json' +import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' +import config from '../config' +import authFetch from '../../../src/rest/authFetch' + +const log = debug('StreamrClient::DataUnionEndpoints::integration-test-withdraw') +// const { log } = console + +// @ts-expect-error +const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +// @ts-expect-error +const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) +const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) +const adminWalletSidechain = new Wallet(config.clientOptions.auth.privateKey, providerSidechain) + +const tokenAdminWallet = new Wallet(config.tokenAdminPrivateKey, providerMainnet) +const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) + +const testWithdraw = async ( + getBalanceBefore: (memberWallet: Wallet, adminTokenMainnet: Contract) => Promise, + withdraw: (dataUnionAddress: string, memberClient: StreamrClient, memberWallet: Wallet, adminClient: StreamrClient) => Promise, + getBalanceAfter: (memberWallet: Wallet, adminTokenMainnet: Contract) => Promise, + requiresMainnetETH: boolean +) => { + log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) + const network = await providerMainnet.getNetwork() + log('Connected to "mainnet" network: ', JSON.stringify(network)) + const network2 = await providerSidechain.getNetwork() + log('Connected to sidechain network: ', JSON.stringify(network2)) + + log(`Minting 100 tokens to ${adminWalletMainnet.address}`) + const tx1 = await tokenMainnet.mint(adminWalletMainnet.address, parseEther('100')) + await tx1.wait() + + const adminClient = new StreamrClient(config.clientOptions as any) + await adminClient.ensureConnected() + + const dataUnion = await adminClient.deployDataUnion() + const secret = await dataUnion.createSecret('DataUnionEndpoints test secret') + log(`DataUnion ${dataUnion.getAddress()} is ready to roll`) + // dataUnion = await adminClient.getDataUnionContract({dataUnion: "0xd778CfA9BB1d5F36E42526B2BAFD07B74b4066c0"}) + + const memberWallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) + const sendTx = await adminWalletSidechain.sendTransaction({ to: memberWallet.address, value: parseEther('0.1') }) + await sendTx.wait() + log(`Sent 0.1 sidechain-ETH to ${memberWallet.address}`) + + if (requiresMainnetETH) { + const send2Tx = await adminWalletMainnet.sendTransaction({ to: memberWallet.address, value: parseEther('0.1') }) + await send2Tx.wait() + log(`Sent 0.1 mainnet-ETH to ${memberWallet.address}`) + } + + const memberClient = new StreamrClient({ + ...config.clientOptions, + auth: { + privateKey: memberWallet.privateKey + } + } as any) + await memberClient.ensureConnected() + + // product is needed for join requests to analyze the DU version + const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') + await authFetch(createProductUrl, adminClient.session, { + method: 'POST', + body: JSON.stringify({ + beneficiaryAddress: dataUnion.getAddress(), + type: 'DATAUNION', + dataUnionVersion: 2 + }) + }) + const res = await memberClient.getDataUnion(dataUnion.getAddress()).join(secret) + // await adminClient.addMembers([memberWallet.address], { dataUnion }) + log(`Member joined data union: ${JSON.stringify(res)}`) + + // eslint-disable-next-line no-underscore-dangle + const contract = await dataUnion._getContract() + const tokenAddress = await contract.token() + log(`Token address: ${tokenAddress}`) + const adminTokenMainnet = new Contract(tokenAddress, Token.abi, adminWalletMainnet) + + const amount = parseEther('1') + const duSidechainEarningsBefore = await contract.sidechain.totalEarnings() + + const duBalance1 = await adminTokenMainnet.balanceOf(dataUnion.getAddress()) + log(`Token balance of ${dataUnion.getAddress()}: ${formatEther(duBalance1)} (${duBalance1.toString()})`) + const balance1 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance1)} (${balance1.toString()})`) + + log(`Transferring ${amount} token-wei ${adminWalletMainnet.address}->${dataUnion.getAddress()}`) + const txTokenToDU = await adminTokenMainnet.transfer(dataUnion.getAddress(), amount) + await txTokenToDU.wait() + + const duBalance2 = await adminTokenMainnet.balanceOf(dataUnion.getAddress()) + log(`Token balance of ${dataUnion.getAddress()}: ${formatEther(duBalance2)} (${duBalance2.toString()})`) + const balance2 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance2)} (${balance2.toString()})`) + + log(`DU member count: ${await contract.sidechain.activeMemberCount()}`) + + log(`Transferred ${formatEther(amount)} tokens, next sending to bridge`) + const tx2 = await contract.sendTokensToBridge() + await tx2.wait() + + log(`Sent to bridge, waiting for the tokens to appear at ${contract.sidechain.address} in sidechain`) + const tokenSidechain = new Contract(config.clientOptions.tokenAddressSidechain, Token.abi, adminWalletSidechain) + await until(async () => !(await tokenSidechain.balanceOf(contract.sidechain.address)).eq('0'), 300000, 3000) + log(`Confirmed tokens arrived, DU balance: ${duSidechainEarningsBefore} -> ${await contract.sidechain.totalEarnings()}`) + + // make a "full" sidechain contract object that has all functions, not just those required by StreamrClient + const sidechainContract = new Contract(contract.sidechain.address, DataUnionSidechain.abi, adminWalletSidechain) + const tx3 = await sidechainContract.refreshRevenue() + const tr3 = await tx3.wait() + log(`refreshRevenue returned ${JSON.stringify(tr3)}`) + log(`DU balance: ${await contract.sidechain.totalEarnings()}`) + + const duBalance3 = await adminTokenMainnet.balanceOf(dataUnion.getAddress()) + log(`Token balance of ${dataUnion.getAddress()}: ${formatEther(duBalance3)} (${duBalance3.toString()})`) + const balance3 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance3)} (${balance3.toString()})`) + + const stats = await memberClient.getDataUnion(dataUnion.getAddress()).getMemberStats(memberWallet.address) + log(`Stats: ${JSON.stringify(stats)}`) + + const balanceBefore = await getBalanceBefore(memberWallet, adminTokenMainnet) + log(`Balance before: ${balanceBefore}. Withdrawing tokens...`) + + const withdrawTr = await withdraw(dataUnion.getAddress(), memberClient, memberWallet, adminClient) + log(`Tokens withdrawn, sidechain tx receipt: ${JSON.stringify(withdrawTr)}`) + const balanceAfter = await getBalanceAfter(memberWallet, adminTokenMainnet) + const balanceIncrease = balanceAfter.sub(balanceBefore) + + await providerMainnet.removeAllListeners() + await providerSidechain.removeAllListeners() + await memberClient.ensureDisconnected() + await adminClient.ensureDisconnected() + + expect(stats).toMatchObject({ + status: 'active', + earningsBeforeLastJoin: '0', + lmeAtJoin: '0', + totalEarnings: '1000000000000000000', + withdrawableEarnings: '1000000000000000000', + }) + expect(withdrawTr.logs[0].address).toBe(config.clientOptions.tokenAddressSidechain) + expect(balanceIncrease.toString()).toBe(amount.toString()) +} + +describe('DataUnion withdraw', () => { + + describe('Member', () => { + + it('by member itself', () => { + const getBalanceBefore = async (memberWallet: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(memberWallet.address) + const withdraw = async (dataUnionAddress: string, memberClient: StreamrClient) => memberClient.getDataUnion(dataUnionAddress).withdrawAll() + const getBalanceAfter = async (memberWallet: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(memberWallet.address) + return testWithdraw(getBalanceBefore, withdraw, getBalanceAfter, true) + }, 300000) + + it('from member to any address', () => { + const outsiderWallet = new Wallet(`0x100000000000000000000000000000000000000012300000002${Date.now()}`, providerSidechain) + const getBalanceBefore = (_: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(outsiderWallet.address) + const withdraw = (dataUnionAddress: string, memberClient: StreamrClient) => memberClient.getDataUnion(dataUnionAddress).withdrawAllTo(outsiderWallet.address) + const getBalanceAfter = (_: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(outsiderWallet.address) + return testWithdraw(getBalanceBefore, withdraw, getBalanceAfter, true) + }, 300000) + + }) + + describe('Admin', () => { + it('non-signed', async () => { + const getBalanceBefore = (memberWallet: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(memberWallet.address) + const withdraw = (dataUnionAddress: string, _: StreamrClient, memberWallet: Wallet, adminClient: StreamrClient) => adminClient.getDataUnion(dataUnionAddress).withdrawAllToMember(memberWallet.address) + const getBalanceAfter = (memberWallet: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(memberWallet.address) + return testWithdraw(getBalanceBefore, withdraw, getBalanceAfter, false) + }, 300000) + + it('signed', async () => { + const member2Wallet = new Wallet(`0x100000000000000000000000000000000000000012300000002${Date.now()}`, providerSidechain) + const getBalanceBefore = (_: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(member2Wallet.address) + const withdraw = async (dataUnionAddress: string, memberClient: StreamrClient, memberWallet: Wallet, adminClient: StreamrClient) => { + const signature = await memberClient.getDataUnion(dataUnionAddress).signWithdrawAllTo(member2Wallet.address) + const withdrawTr = await adminClient.getDataUnion(dataUnionAddress).withdrawAllToSigned(memberWallet.address, member2Wallet.address, signature) + return withdrawTr + } + const getBalanceAfter = (_: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(member2Wallet.address) + return testWithdraw(getBalanceBefore, withdraw, getBalanceAfter, false) + }, 300000) + }) + +}) From 49642da24586d7c89e325fad21597d166320c7f4 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 13:20:33 -0500 Subject: [PATCH 470/517] Add npm run test-types to check ./src & ./test types. --- .github/workflows/nodejs.yml | 2 ++ package.json | 1 + tsconfig.test.json | 5 +++++ 3 files changed, 8 insertions(+) create mode 100644 tsconfig.test.json diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 5051e46c5..13104dfd5 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -48,6 +48,8 @@ jobs: run: npm ci - name: npm run eslint run: npm run eslint + - name: test-types + run: npm run test-types test: name: Test Unit using Node ${{ matrix.node-version }} diff --git a/package.json b/package.json index 119b4bae7..085914a4b 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "eslint": "eslint --cache-location=node_modules/.cache/.eslintcache/ '*/**/*.{js,ts}'", "test": "jest --detectOpenHandles", "test-unit": "jest test/unit --detectOpenHandles", + "test-types": "tsc --noEmit --project ./tsconfig.test.json", "coverage": "jest --coverage", "test-integration": "jest --forceExit test/integration", "test-integration-no-resend": "jest --forceExit --testTimeout=10000 --testPathIgnorePatterns='resend|Resend' --testNamePattern='^((?!(resend|Resend|resent|Resent|gap|Gap)).)*$' test/integration/*.test.js", diff --git a/tsconfig.test.json b/tsconfig.test.json new file mode 100644 index 000000000..bd467276a --- /dev/null +++ b/tsconfig.test.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*", "contracts/**/*", "test/**/*"], + "exclude": ["node_modules", "dist", "test/legacy/*"] +} From 2dad5d9d0f573bdae98575a4b8086091632e4be3 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 13:22:56 -0500 Subject: [PATCH 471/517] Linting & clean up DataUnion jsdoc + types. --- src/rest/DataUnionEndpoints.ts | 177 +++++++++++------- .../dataunion/DataUnionEndpoints.test.ts | 10 +- test/integration/dataunion/deploy.test.ts | 2 +- tsconfig.json | 7 +- 4 files changed, 115 insertions(+), 81 deletions(-) diff --git a/src/rest/DataUnionEndpoints.ts b/src/rest/DataUnionEndpoints.ts index 2ebc45e04..f2205677e 100644 --- a/src/rest/DataUnionEndpoints.ts +++ b/src/rest/DataUnionEndpoints.ts @@ -279,7 +279,7 @@ const sidechainAmbABI = [{ // Contract utils // ////////////////////////////////////////////////////////////////// -/** @typedef {String} EthereumAddress */ +type EthereumAddress = string function throwIfBadAddress(address: string, variableDescription: Todo) { try { @@ -295,7 +295,7 @@ async function getSidechainAmb(client: StreamrClient) { if (!cachedSidechainAmb) { const getAmbPromise = async () => { const mainnetProvider = client.ethereum.getMainnetProvider() - const factoryMainnetAddress = client.options.factoryMainnetAddress + const { factoryMainnetAddress } = client.options const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, mainnetProvider) const sidechainProvider = client.ethereum.getSidechainProvider() const factorySidechainAddress = await factoryMainnet.data_union_sidechain_factory() // TODO use getDataUnionSidechainAddress() @@ -319,7 +319,7 @@ async function getSidechainAmb(client: StreamrClient) { async function getMainnetAmb(client: StreamrClient) { const mainnetProvider = client.ethereum.getMainnetProvider() - const factoryMainnetAddress = client.options.factoryMainnetAddress + const { factoryMainnetAddress } = client.options const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, mainnetProvider) const mainnetAmbAddress = await factoryMainnet.amb() return new Contract(mainnetAmbAddress, mainnetAmbABI, mainnetProvider) @@ -340,7 +340,7 @@ async function requiredSignaturesHaveBeenCollected(client: StreamrClient, messag } // move signatures from sidechain to mainnet -async function transportSignatures(client: StreamrClient, messageHash: Todo) { +async function transportSignatures(client: StreamrClient, messageHash: string) { const sidechainAmb = await getSidechainAmb(client) const message = await sidechainAmb.message(messageHash) const messageId = '0x' + message.substr(2, 64) @@ -483,11 +483,16 @@ async function untilWithdrawIsComplete( // TODO remove caching as we calculate the values only when deploying the DU const mainnetAddressCache: Todo = {} // mapping: "name" -> mainnet address -/** @returns {Promise} Mainnet address for Data Union */ -async function fetchDataUnionMainnetAddress(client: StreamrClient, dataUnionName: string, deployerAddress: string) { + +/** @returns Mainnet address for Data Union */ +async function fetchDataUnionMainnetAddress( + client: StreamrClient, + dataUnionName: string, + deployerAddress: EthereumAddress +): Promise { if (!mainnetAddressCache[dataUnionName]) { const provider = client.ethereum.getMainnetProvider() - const factoryMainnetAddress = client.options.factoryMainnetAddress + const { factoryMainnetAddress } = client.options const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, provider) const addressPromise = factoryMainnet.mainnetAddress(deployerAddress, dataUnionName) mainnetAddressCache[dataUnionName] = addressPromise @@ -496,8 +501,8 @@ async function fetchDataUnionMainnetAddress(client: StreamrClient, dataUnionName return mainnetAddressCache[dataUnionName] } -function getDataUnionMainnetAddress(client: StreamrClient, dataUnionName: string, deployerAddress: string) { - const factoryMainnetAddress = client.options.factoryMainnetAddress +function getDataUnionMainnetAddress(client: StreamrClient, dataUnionName: string, deployerAddress: EthereumAddress) { + const { factoryMainnetAddress } = client.options // NOTE! this must be updated when DU sidechain smartcontract changes: keccak256(CloneLib.cloneBytecode(data_union_mainnet_template)); const codeHash = '0x50a78bac973bdccfc8415d7d9cfd62898b8f7cf6e9b3a15e7d75c0cb820529eb' const salt = keccak256(defaultAbiCoder.encode(['string', 'address'], [dataUnionName, deployerAddress])) @@ -506,11 +511,11 @@ function getDataUnionMainnetAddress(client: StreamrClient, dataUnionName: string // TODO remove caching as we calculate the values only when deploying the DU const sidechainAddressCache: Todo = {} // mapping: mainnet address -> sidechain address -/** @returns {Promise} Sidechain address for Data Union */ -async function fetchDataUnionSidechainAddress(client: StreamrClient, duMainnetAddress: string) { +/** @returns Sidechain address for Data Union */ +async function fetchDataUnionSidechainAddress(client: StreamrClient, duMainnetAddress: EthereumAddress): Promise { if (!sidechainAddressCache[duMainnetAddress]) { const provider = client.ethereum.getMainnetProvider() - const factoryMainnetAddress = client.options.factoryMainnetAddress + const { factoryMainnetAddress } = client.options const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, provider) const addressPromise = factoryMainnet.sidechainAddress(duMainnetAddress) sidechainAddressCache[duMainnetAddress] = addressPromise @@ -519,14 +524,14 @@ async function fetchDataUnionSidechainAddress(client: StreamrClient, duMainnetAd return sidechainAddressCache[duMainnetAddress] } -function getDataUnionSidechainAddress(client: StreamrClient, mainnetAddress: string) { - const factorySidechainAddress = client.options.factorySidechainAddress +function getDataUnionSidechainAddress(client: StreamrClient, mainnetAddress: EthereumAddress) { + const { factorySidechainAddress } = client.options // NOTE! this must be updated when DU sidechain smartcontract changes: keccak256(CloneLib.cloneBytecode(data_union_sidechain_template)) const codeHash = '0x040cf686e25c97f74a23a4bf01c29dd77e260c4b694f5611017ce9713f58de83' return getCreate2Address(factorySidechainAddress, hexZeroPad(mainnetAddress, 32), codeHash) } -function getMainnetContractReadOnly(contractAddress: string, client: StreamrClient) { +function getMainnetContractReadOnly(contractAddress: EthereumAddress, client: StreamrClient) { if (isAddress(contractAddress)) { const provider = client.ethereum.getMainnetProvider() return new Contract(contractAddress, dataUnionMainnetABI, provider) @@ -535,14 +540,14 @@ function getMainnetContractReadOnly(contractAddress: string, client: StreamrClie } } -function getMainnetContract(contractAddress: string, client: StreamrClient) { +function getMainnetContract(contractAddress: EthereumAddress, client: StreamrClient) { const du = getMainnetContractReadOnly(contractAddress, client) const signer = client.ethereum.getSigner() // @ts-expect-error return du.connect(signer) } -async function getSidechainContract(contractAddress: string, client: StreamrClient) { +async function getSidechainContract(contractAddress: EthereumAddress, client: StreamrClient) { const signer = await client.ethereum.getSidechainSigner() const duMainnet = getMainnetContractReadOnly(contractAddress, client) const duSidechainAddress = getDataUnionSidechainAddress(client, duMainnet.address) @@ -551,8 +556,8 @@ async function getSidechainContract(contractAddress: string, client: StreamrClie return duSidechain } -async function getSidechainContractReadOnly(contractAddress: string, client: StreamrClient) { - const provider = await client.ethereum.getSidechainProvider() +async function getSidechainContractReadOnly(contractAddress: EthereumAddress, client: StreamrClient) { + const provider = client.ethereum.getSidechainProvider() const duMainnet = getMainnetContractReadOnly(contractAddress, client) const duSidechainAddress = getDataUnionSidechainAddress(client, duMainnet.address) // @ts-expect-error @@ -573,13 +578,13 @@ export class DataUnionEndpoints { // ////////////////////////////////////////////////////////////////// // TODO inline this function? - calculateDataUnionMainnetAddress(dataUnionName: string, deployerAddress: string) { + calculateDataUnionMainnetAddress(dataUnionName: string, deployerAddress: EthereumAddress) { const address = getAddress(deployerAddress) // throws if bad address return getDataUnionMainnetAddress(this.client, dataUnionName, address) } // TODO inline this function? - calculateDataUnionSidechainAddress(duMainnetAddress: string) { + calculateDataUnionSidechainAddress(duMainnetAddress: EthereumAddress) { const address = getAddress(duMainnetAddress) // throws if bad address return getDataUnionSidechainAddress(this.client, address) } @@ -587,7 +592,7 @@ export class DataUnionEndpoints { /** * Create a new DataUnionMainnet contract to mainnet with DataUnionFactoryMainnet * This triggers DataUnionSidechain contract creation in sidechain, over the bridge (AMB) - * @return {Promise} that resolves when the new DU is deployed over the bridge to side-chain + * @return that resolves when the new DU is deployed over the bridge to side-chain */ async deployDataUnion(options: DataUnionDeployOptions = {}): Promise { const { @@ -678,7 +683,7 @@ export class DataUnionEndpoints { return dataUnion } - async getContract(contractAddress: string) { + async getContract(contractAddress: EthereumAddress) { const ret = getMainnetContract(contractAddress, this.client) // @ts-expect-error ret.sidechain = await getSidechainContract(contractAddress, this.client) @@ -688,7 +693,7 @@ export class DataUnionEndpoints { /** * Add a new data union secret */ - async createSecret(dataUnionMainnetAddress: string, name: string = 'Untitled Data Union Secret'): Promise { + async createSecret(dataUnionMainnetAddress: EthereumAddress, name: string = 'Untitled Data Union Secret'): Promise { const duAddress = getAddress(dataUnionMainnetAddress) // throws if bad address const url = getEndpointUrl(this.client.options.restUrl, 'dataunions', duAddress, 'secrets') const res = await authFetch( @@ -714,7 +719,11 @@ export class DataUnionEndpoints { /** * Add given Ethereum addresses as data union members */ - async addMembers(memberAddressList: string[], options: DataUnionMemberListModificationOptions|undefined = {}, contractAddress: string): Promise { + async addMembers( + memberAddressList: string[], + options: DataUnionMemberListModificationOptions|undefined = {}, + contractAddress: EthereumAddress + ): Promise { const members = memberAddressList.map(getAddress) // throws if there are bad addresses const duSidechain = await getSidechainContract(contractAddress, this.client) const tx = await duSidechain.addMembers(members) @@ -726,7 +735,11 @@ export class DataUnionEndpoints { /** * Remove given members from data union */ - async removeMembers(memberAddressList: string[], options: DataUnionMemberListModificationOptions|undefined = {}, contractAddress: string): Promise { + async removeMembers( + memberAddressList: string[], + options: DataUnionMemberListModificationOptions|undefined = {}, + contractAddress: EthereumAddress + ): Promise { const members = memberAddressList.map(getAddress) // throws if there are bad addresses const duSidechain = await getSidechainContract(contractAddress, this.client) const tx = await duSidechain.partMembers(members) @@ -738,11 +751,14 @@ export class DataUnionEndpoints { /** * Admin: withdraw earnings (pay gas) on behalf of a member * TODO: add test - * @param {EthereumAddress} memberAddress the other member who gets their tokens out of the Data Union - * @param options - * @returns {Promise} get receipt once withdraw transaction is confirmed + * @param memberAddress - the other member who gets their tokens out of the Data Union + * @returns Receipt once withdraw transaction is confirmed */ - async withdrawAllToMember(memberAddress: string, options: DataUnionWithdrawOptions|undefined, contractAddress: string): Promise { + async withdrawAllToMember( + memberAddress: EthereumAddress, + options: DataUnionWithdrawOptions|undefined, + contractAddress: EthereumAddress + ): Promise { const address = getAddress(memberAddress) // throws if bad address const tr = await untilWithdrawIsComplete( this.client, @@ -755,10 +771,10 @@ export class DataUnionEndpoints { /** * Admin: get the tx promise for withdrawing all earnings on behalf of a member - * @param {EthereumAddress} memberAddress the other member who gets their tokens out of the Data Union - * @returns {Promise} await on call .wait to actually send the tx + * @param memberAddress - the other member who gets their tokens out of the Data Union + * @returns await on call .wait to actually send the tx */ - async getWithdrawAllToMemberTx(memberAddress: string, contractAddress: string): Promise { + async getWithdrawAllToMemberTx(memberAddress: EthereumAddress, contractAddress: EthereumAddress): Promise { const a = getAddress(memberAddress) // throws if bad address const duSidechain = await getSidechainContract(contractAddress, this.client) return duSidechain.withdrawAll(a, true) // sendToMainnet=true @@ -766,12 +782,18 @@ export class DataUnionEndpoints { /** * Admin: Withdraw a member's earnings to another address, signed by the member - * @param {EthereumAddress} memberAddress the member whose earnings are sent out - * @param {EthereumAddress} recipientAddress the address to receive the tokens in mainnet - * @param {string} signature from member, produced using signWithdrawAllTo - * @returns {Promise} get receipt once withdraw transaction is confirmed + * @param memberAddress - the member whose earnings are sent out + * @param recipientAddress - the address to receive the tokens in mainnet + * @param signature - from member, produced using signWithdrawAllTo + * @returns receipt once withdraw transaction is confirmed */ - async withdrawAllToSigned(memberAddress: string, recipientAddress: string, signature: string, options: DataUnionWithdrawOptions|undefined, contractAddress: string): Promise { + async withdrawAllToSigned( + memberAddress: EthereumAddress, + recipientAddress: EthereumAddress, + signature: string, + options: DataUnionWithdrawOptions|undefined, + contractAddress: EthereumAddress + ): Promise { const from = getAddress(memberAddress) // throws if bad address const to = getAddress(recipientAddress) const tr = await untilWithdrawIsComplete( @@ -785,12 +807,17 @@ export class DataUnionEndpoints { /** * Admin: Withdraw a member's earnings to another address, signed by the member - * @param {EthereumAddress} memberAddress the member whose earnings are sent out - * @param {EthereumAddress} recipientAddress the address to receive the tokens in mainnet - * @param {string} signature from member, produced using signWithdrawAllTo - * @returns {Promise} await on call .wait to actually send the tx + * @param memberAddress - the member whose earnings are sent out + * @param recipientAddress - the address to receive the tokens in mainnet + * @param signature - from member, produced using signWithdrawAllTo + * @returns await on call .wait to actually send the tx */ - async getWithdrawAllToSignedTx(memberAddress: string, recipientAddress: string, signature: string, contractAddress: string): Promise { + async getWithdrawAllToSignedTx( + memberAddress: EthereumAddress, + recipientAddress: EthereumAddress, + signature: string, + contractAddress: EthereumAddress + ): Promise { const duSidechain = await getSidechainContract(contractAddress, this.client) return duSidechain.withdrawAllToSigned(memberAddress, recipientAddress, true, signature) // sendToMainnet=true } @@ -798,7 +825,7 @@ export class DataUnionEndpoints { /** * Admin: set admin fee (between 0.0 and 1.0) for the data union */ - async setAdminFee(newFeeFraction: number, contractAddress: string): Promise { + async setAdminFee(newFeeFraction: number, contractAddress: EthereumAddress): Promise { if (newFeeFraction < 0 || newFeeFraction > 1) { throw new Error('newFeeFraction argument must be a number between 0...1, got: ' + newFeeFraction) } @@ -811,13 +838,13 @@ export class DataUnionEndpoints { /** * Get data union admin fee fraction (between 0.0 and 1.0) that admin gets from each revenue event */ - async getAdminFee(contractAddress: string): Promise { + async getAdminFee(contractAddress: EthereumAddress): Promise { const duMainnet = getMainnetContractReadOnly(contractAddress, this.client) const adminFeeBN = await duMainnet.adminFeeFraction() return +adminFeeBN.toString() / 1e18 } - async getAdminAddress(contractAddress: string): Promise { + async getAdminAddress(contractAddress: EthereumAddress): Promise { const duMainnet = getMainnetContractReadOnly(contractAddress, this.client) return duMainnet.owner() } @@ -829,8 +856,8 @@ export class DataUnionEndpoints { /** * Send a joinRequest, or get into data union instantly with a data union secret */ - async join(secret: string|undefined, contractAddress: string): Promise { - const memberAddress = this.client.getAddress() + async join(secret: string|undefined, contractAddress: EthereumAddress): Promise { + const memberAddress = this.client.getAddress() as string const body: any = { memberAddress } @@ -854,7 +881,7 @@ export class DataUnionEndpoints { return response } - async isMember(memberAddress: string, contractAddress: string): Promise { + async isMember(memberAddress: EthereumAddress, contractAddress: EthereumAddress): Promise { const address = getAddress(memberAddress) const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) const ACTIVE = 1 // memberData[0] is enum ActiveStatus {None, Active, Inactive} @@ -864,14 +891,14 @@ export class DataUnionEndpoints { } // TODO: this needs more thought: probably something like getEvents from sidechain? Heavy on RPC? - async getMembers(contractAddress: string) { + async getMembers(contractAddress: EthereumAddress) { const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) throw new Error(`Not implemented for side-chain data union (at ${duSidechain.address})`) // event MemberJoined(address indexed); // event MemberParted(address indexed); } - async getStats(contractAddress: string): Promise { + async getStats(contractAddress: EthereumAddress): Promise { const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) const [ totalEarnings, @@ -895,7 +922,7 @@ export class DataUnionEndpoints { /** * Get stats of a single data union member */ - async getMemberStats(memberAddress: string, contractAddress: string): Promise { + async getMemberStats(memberAddress: EthereumAddress, contractAddress: EthereumAddress): Promise { const address = getAddress(memberAddress) // TODO: use duSidechain.getMemberStats(address) once it's implemented, to ensure atomic read // (so that memberData is from same block as getEarnings, otherwise withdrawable will be foobar) @@ -916,7 +943,7 @@ export class DataUnionEndpoints { /** * Get the amount of tokens the member would get from a successful withdraw */ - async getWithdrawableEarnings(memberAddress: string, contractAddress: string): Promise { + async getWithdrawableEarnings(memberAddress: EthereumAddress, contractAddress: EthereumAddress): Promise { const address = getAddress(memberAddress) const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) return duSidechain.getWithdrawableEarnings(address) @@ -944,7 +971,7 @@ export class DataUnionEndpoints { * Figure out if given mainnet address is old DataUnion (v 1.0) or current 2.0 * NOTE: Current version of streamr-client-javascript can only handle current version! */ - async getVersion(contractAddress: string): Promise { + async getVersion(contractAddress: EthereumAddress): Promise { const a = getAddress(contractAddress) // throws if bad address const provider = this.client.ethereum.getMainnetProvider() const du = new Contract(a, [{ @@ -969,9 +996,9 @@ export class DataUnionEndpoints { /** * Withdraw all your earnings - * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) + * @returns receipt once withdraw is complete (tokens are seen in mainnet) */ - async withdrawAll(contractAddress: string, options?: DataUnionWithdrawOptions): Promise { + async withdrawAll(contractAddress: EthereumAddress, options?: DataUnionWithdrawOptions): Promise { const recipientAddress = this.client.getAddress() const tr = await untilWithdrawIsComplete( this.client, @@ -984,9 +1011,9 @@ export class DataUnionEndpoints { /** * Get the tx promise for withdrawing all your earnings - * @returns {Promise} await on call .wait to actually send the tx + * @returns await on call .wait to actually send the tx */ - async getWithdrawAllTx(contractAddress: string): Promise { + async getWithdrawAllTx(contractAddress: EthereumAddress): Promise { const signer = await this.client.ethereum.getSidechainSigner() // @ts-expect-error const address = await signer.getAddress() @@ -1006,9 +1033,13 @@ export class DataUnionEndpoints { /** * Withdraw earnings and "donate" them to the given address - * @returns {Promise} get receipt once withdraw is complete (tokens are seen in mainnet) + * @returns get receipt once withdraw is complete (tokens are seen in mainnet) */ - async withdrawAllTo(recipientAddress: string, options: DataUnionWithdrawOptions|undefined, contractAddress: string): Promise { + async withdrawAllTo( + recipientAddress: EthereumAddress, + options: DataUnionWithdrawOptions|undefined, + contractAddress: EthereumAddress + ): Promise { const to = getAddress(recipientAddress) // throws if bad address const tr = await untilWithdrawIsComplete( this.client, @@ -1021,11 +1052,10 @@ export class DataUnionEndpoints { /** * Withdraw earnings and "donate" them to the given address - * @param {EthereumAddress} recipientAddress the address to receive the tokens - * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) - * @returns {Promise} await on call .wait to actually send the tx + * @param recipientAddress - the address to receive the tokens + * @returns await on call .wait to actually send the tx */ - async getWithdrawAllToTx(recipientAddress: string, contractAddress: string): Promise { + async getWithdrawAllToTx(recipientAddress: EthereumAddress, contractAddress: EthereumAddress): Promise { const signer = await this.client.ethereum.getSidechainSigner() // @ts-expect-error const address = await signer.getAddress() @@ -1047,10 +1077,10 @@ export class DataUnionEndpoints { * Admin can execute the withdraw using this signature: ``` * await adminStreamrClient.withdrawAllToSigned(memberAddress, recipientAddress, signature) * ``` - * @param {EthereumAddress} recipientAddress the address authorized to receive the tokens - * @returns {string} signature authorizing withdrawing all earnings to given recipientAddress + * @param recipientAddress - the address authorized to receive the tokens + * @returns signature authorizing withdrawing all earnings to given recipientAddress */ - async signWithdrawAllTo(recipientAddress: string, contractAddress: string): Promise { + async signWithdrawAllTo(recipientAddress: EthereumAddress, contractAddress: EthereumAddress): Promise { return this.signWithdrawAmountTo(recipientAddress, BigNumber.from(0), contractAddress) } @@ -1058,12 +1088,15 @@ export class DataUnionEndpoints { * Member can sign off to "donate" specific amount of earnings to another address such that someone else * can submit the transaction (and pay for the gas) * This signature is only valid until next withdrawal takes place (using this signature or otherwise). - * @param {EthereumAddress} recipientAddress the address authorized to receive the tokens - * @param {BigNumber|number|string} amountTokenWei that the signature is for (can't be used for less or for more) - * @param {EthereumOptions} options (including e.g. `dataUnion` Contract object or address) - * @returns {string} signature authorizing withdrawing all earnings to given recipientAddress + * @param recipientAddress - the address authorized to receive the tokens + * @param amountTokenWei - that the signature is for (can't be used for less or for more) + * @returns signature authorizing withdrawing all earnings to given recipientAddress */ - async signWithdrawAmountTo(recipientAddress: string, amountTokenWei: BigNumber|number|string, contractAddress: string): Promise { + async signWithdrawAmountTo( + recipientAddress: EthereumAddress, + amountTokenWei: BigNumber|number|string, + contractAddress: EthereumAddress + ): Promise { const to = getAddress(recipientAddress) // throws if bad address const signer = this.client.ethereum.getSigner() // it shouldn't matter if it's mainnet or sidechain signer since key should be the same // @ts-expect-error diff --git a/test/integration/dataunion/DataUnionEndpoints.test.ts b/test/integration/dataunion/DataUnionEndpoints.test.ts index 8182d4a5d..da5681917 100644 --- a/test/integration/dataunion/DataUnionEndpoints.test.ts +++ b/test/integration/dataunion/DataUnionEndpoints.test.ts @@ -26,12 +26,12 @@ describe('DataUnionEndPoints', () => { const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) afterAll(async () => { - await providerMainnet.removeAllListeners() - await providerSidechain.removeAllListeners() + providerMainnet.removeAllListeners() + providerSidechain.removeAllListeners() await adminClient.ensureDisconnected() }) - const streamrClientCleanupList = [] + const streamrClientCleanupList: StreamrClient[] = [] afterAll(async () => Promise.all(streamrClientCleanupList.map((c) => c.ensureDisconnected()))) beforeAll(async () => { @@ -51,7 +51,7 @@ describe('DataUnionEndPoints', () => { // fresh dataUnion for each test case, created NOT in parallel to avoid nonce troubles const adminMutex = new Mutex() - async function deployDataUnionSync(testName) { + async function deployDataUnionSync(testName: string) { let dataUnion: DataUnion await adminMutex.runExclusive(async () => { const dataUnionName = testName + Date.now() @@ -74,7 +74,7 @@ describe('DataUnionEndPoints', () => { } ) }) - return dataUnion + return dataUnion! } describe('Admin', () => { diff --git a/test/integration/dataunion/deploy.test.ts b/test/integration/dataunion/deploy.test.ts index 943e0a50b..aad865cca 100644 --- a/test/integration/dataunion/deploy.test.ts +++ b/test/integration/dataunion/deploy.test.ts @@ -15,7 +15,7 @@ const createMockAddress = () => '0x000000000000000000000000000' + Date.now() describe('DataUnion deployment', () => { - let adminClient + let adminClient: StreamrClient beforeAll(async () => { log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) diff --git a/tsconfig.json b/tsconfig.json index 277e2659b..4a3cef9df 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,8 +8,9 @@ "outDir": "dist", "strict": true, "esModuleInterop": true, - "resolveJsonModule": true + "resolveJsonModule": true, + "moduleResolution": "node" }, - "include": ["src/**/*"], + "include": ["src/**/*", "contracts/*"], "exclude": ["node_modules", "dist"] -} \ No newline at end of file +} From eae6929879d2775a66397076bbbb3e17cb1d131f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 17 Feb 2021 15:04:54 -0500 Subject: [PATCH 472/517] Move Config types into Config.ts. --- src/Config.ts | 48 ++++++++++++++++++++++++++++++++++++++++++-- src/StreamrClient.ts | 47 +++++++------------------------------------ 2 files changed, 53 insertions(+), 42 deletions(-) diff --git a/src/Config.ts b/src/Config.ts index 711871e92..07a567e65 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -1,9 +1,53 @@ import qs from 'qs' -import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import Debug from 'debug' +import { ControlLayer, MessageLayer } from 'streamr-client-protocol' +import { ExternalProvider, JsonRpcFetchFunc } from '@ethersproject/providers' +import { BigNumber } from '@ethersproject/bignumber' import { getVersionString, counterId } from './utils' -import { StreamrClientOptions } from './StreamrClient' +import { Todo } from './types' + +export interface StreamrClientOptions { + id?: string + debug?: Debug.Debugger, + auth?: { + privateKey?: string + ethereum?: ExternalProvider|JsonRpcFetchFunc, + apiKey?: string + username?: string + password?: string + } + url?: string + restUrl?: string + streamrNodeAddress?: string + autoConnect?: boolean + autoDisconnect?: boolean + orderMessages?: boolean, + retryResendAfter?: number, + gapFillTimeout?: number, + maxGapRequests?: number, + maxPublishQueueSize?: number, + publishWithSignature?: Todo, + verifySignatures?: Todo, + publisherStoreKeyHistory?: boolean, + groupKeys?: Todo + keyExchange?: Todo + mainnet?: Todo + sidechain?: { + url?: string + }, + dataUnion?: string + tokenAddress?: string, + minimumWithdrawTokenWei?: BigNumber|number|string, + sidechainTokenAddress?: string + factoryMainnetAddress?: string + sidechainAmbAddress?: string + payForSignatureTransport?: boolean + cache?: { + maxSize?: number, + maxAge?: number + } +} const { ControlMessage } = ControlLayer const { StreamMessage } = MessageLayer diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index f104228f6..0d8ad7291 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -4,7 +4,7 @@ import Debug from 'debug' import { counterId, uuid, CacheAsyncFn } from './utils' import { validateOptions } from './stream/utils' -import Config from './Config' +import Config, { StreamrClientOptions } from './Config' import StreamrEthereum from './Ethereum' import Session from './Session' import Connection, { ConnectionError } from './Connection' @@ -21,52 +21,19 @@ import { ExternalProvider, JsonRpcFetchFunc } from '@ethersproject/providers' import { DataUnion, DataUnionDeployOptions } from './dataunion/DataUnion' import { getAddress } from '@ethersproject/address' -export interface StreamrClientOptions { - id?: string - debug?: Debug.Debugger, - auth?: { - privateKey?: string - ethereum?: ExternalProvider|JsonRpcFetchFunc, - apiKey?: string - username?: string - password?: string - } - url?: string - restUrl?: string - streamrNodeAddress?: string - autoConnect?: boolean - autoDisconnect?: boolean - orderMessages?: boolean, - retryResendAfter?: number, - gapFillTimeout?: number, - maxPublishQueueSize?: number, - publishWithSignature?: Todo, - verifySignatures?: Todo, - publisherStoreKeyHistory?: boolean, - groupKeys?: Todo - keyExchange?: Todo - mainnet?: Todo - sidechain?: { - url?: string - }, - tokenAddress: string, - minimumWithdrawTokenWei?: BigNumber|number|string, - factoryMainnetAddress: string - factorySidechainAddress: string - payForSignatureTransport?: boolean - cache?: { - maxSize?: number, - maxAge?: number - } -} +type Function = (...args: any[]) => any // Utility Type: generic function +type Promisify = (...args: Parameters) => Promise> // Utility Type: make a function async +type MaybeAsync = F | Promisify // Utility Type: make a function maybe async // TODO get metadata type from streamr-protocol-js project (it doesn't export the type definitions yet) -export type OnMessageCallback = (message: any, metadata: any) => void +export type OnMessageCallback = MaybeAsync<(message: any, metadata: any) => void> interface MessageEvent { data: any } +export { StreamrClientOptions } + /** * Wrap connection message events with message parsing. */ From 29f888f17688972270b77b16a899e169e35373ad Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Wed, 17 Feb 2021 16:17:04 -0500 Subject: [PATCH 473/517] Automatically attach rest endpoint methods to client, inherit method signatures directly. --- src/Ethereum.js | 41 +++++++----- src/StreamrClient.ts | 118 +++++++++------------------------ src/rest/DataUnionEndpoints.ts | 2 +- 3 files changed, 59 insertions(+), 102 deletions(-) diff --git a/src/Ethereum.js b/src/Ethereum.js index 19bf0bfaf..ad0b91725 100644 --- a/src/Ethereum.js +++ b/src/Ethereum.js @@ -1,6 +1,7 @@ import { Wallet } from '@ethersproject/wallet' import { getDefaultProvider, JsonRpcProvider, Web3Provider } from '@ethersproject/providers' import { computeAddress } from '@ethersproject/transactions' +import { getAddress } from '@ethersproject/address' export default class StreamrEthereum { static generateEthereumAccount() { @@ -17,18 +18,23 @@ export default class StreamrEthereum { const { auth } = options if (auth.privateKey) { const key = auth.privateKey - const address = computeAddress(key) - this.getAddress = () => address + const address = getAddress(computeAddress(key)) + this._getAddress = () => address this.getSigner = () => new Wallet(key, this.getMainnetProvider()) this.getSidechainSigner = async () => new Wallet(key, this.getSidechainProvider()) } else if (auth.ethereum) { - this.getAddress = () => auth.ethereum.selectedAddress // null if no addresses connected+selected in Metamask - this.getSigner = () => { + this._getAddress = () => { + if (auth.ethereum.selectedAddress) { + throw new Error('no addresses connected+selected in Metamask') + } + return getAddress(auth.ethereum.selectedAddress) + } + this._getSigner = () => { const metamaskProvider = new Web3Provider(auth.ethereum) const metamaskSigner = metamaskProvider.getSigner() return metamaskSigner } - this.getSidechainSigner = async () => { + this._getSidechainSigner = async () => { // chainId is required for checking when using Metamask if (!options.sidechain || !options.sidechain.chainId) { throw new Error('Streamr sidechain not configured (with chainId) in the StreamrClient options!') @@ -51,25 +57,30 @@ export default class StreamrEthereum { } } - /* eslint-disable class-methods-use-this */ - getAddress() { - // default. should be overridden in constructor based on options - return null + if (!this._getAddress) { + // _getAddress is assigned in constructor + throw new Error('StreamrClient is not authenticated with private key') + } + return this._getAddress() } getSigner() { - // default. should be overridden in constructor based on options - throw new Error("StreamrClient not authenticated! Can't send transactions or sign messages.") + if (!this._getSigner) { + // _getSigner is assigned in constructor + throw new Error("StreamrClient not authenticated! Can't send transactions or sign messages.") + } + return this._getSigner() } async getSidechainSigner() { - // default. should be overridden in constructor based on options - throw new Error("StreamrClient not authenticated! Can't send transactions or sign messages.") + if (!this._getSidechainSigner) { + // _getSidechainSigner is assigned in constructor + throw new Error("StreamrClient not authenticated! Can't send transactions or sign messages.") + } + return this._getSidechainSigner() } - /* eslint-enable class-methods-use-this */ - /** @returns Ethers.js Provider, a connection to the Ethereum network (mainnet) */ getMainnetProvider() { if (this.client.options.mainnet) { diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index 0d8ad7291..5b05258ac 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -12,14 +12,10 @@ import Publisher from './publish' import Subscriber from './subscribe' import { getUserId } from './user' import { Todo } from './types' -import { StreamEndpoints, StreamListQuery } from './rest/StreamEndpoints' +import { StreamEndpoints } from './rest/StreamEndpoints' import { LoginEndpoints } from './rest/LoginEndpoints' import { DataUnionEndpoints } from './rest/DataUnionEndpoints' -import { BigNumber } from '@ethersproject/bignumber' -import Stream, { StreamProperties } from './stream' -import { ExternalProvider, JsonRpcFetchFunc } from '@ethersproject/providers' import { DataUnion, DataUnionDeployOptions } from './dataunion/DataUnion' -import { getAddress } from '@ethersproject/address' type Function = (...args: any[]) => any // Utility Type: generic function type Promisify = (...args: Parameters) => Promise> // Utility Type: make a function async @@ -131,8 +127,24 @@ class StreamrCached { // use process id in node uid const uid = process.pid != null ? process.pid : `${uuid().slice(-4)}${uuid().slice(0, 4)}` -export default class StreamrClient extends EventEmitter { +/** + * Take prototype functions from srcInstance and attach them to targetInstance while keeping them bound to srcInstance. + */ +function Plugin(targetInstance: any, srcInstance: any) { + Object.getOwnPropertyNames(srcInstance.constructor.prototype).forEach((name) => { + const value = srcInstance.constructor.prototype[name] + if (typeof value !== 'function') { return } + // eslint-disable-next-line no-param-reassign + targetInstance[name] = srcInstance[name].bind(srcInstance) + }) + return srcInstance +} + +// these are mixed in via Plugin function above +interface StreamrClient extends StreamEndpoints, LoginEndpoints, DataUnionEndpoints {} +// eslint-disable-next-line no-redeclare +class StreamrClient extends EventEmitter { id: string debug: Debug.Debugger options: StreamrClientOptions @@ -181,15 +193,14 @@ export default class StreamrClient extends EventEmitter { .on('disconnected', this.onConnectionDisconnected) .on('error', this.onConnectionError) - // @ts-expect-error - this.publisher = new Publisher(this) + this.publisher = Publisher(this) this.subscriber = new Subscriber(this) - this.cached = new StreamrCached(this) this.ethereum = new StreamrEthereum(this) - this.streamEndpoints = new StreamEndpoints(this) - this.loginEndpoints = new LoginEndpoints(this) - this.dataUnionEndpoints = new DataUnionEndpoints(this) + this.streamEndpoints = Plugin(this, new StreamEndpoints(this)) + this.loginEndpoints = Plugin(this, new LoginEndpoints(this)) + this.dataUnionEndpoints = Plugin(this, new DataUnionEndpoints(this)) + this.cached = new StreamrCached(this) } async onConnectionConnected() { @@ -365,91 +376,20 @@ export default class StreamrClient extends EventEmitter { return this.connection.enableAutoDisconnect(...args) } - getAddress(): string { - const address = this.ethereum.getAddress() - if (address) { - return getAddress(address) - } else { - throw new Error('StreamrClient is not authenticated with private key') - } + getAddress() { + return this.ethereum.getAddress() } async getPublisherId() { return this.getAddress() } - static generateEthereumAccount() { - return StreamrEthereum.generateEthereumAccount() - } - - // TODO many of these methods that use streamEndpoints/loginEndpoints/dataUnionEndpoints are private: remove those - - async getStream(streamId: string) { - return this.streamEndpoints.getStream(streamId) - } - - async listStreams(query: StreamListQuery = {}) { - return this.streamEndpoints.listStreams(query) - } - - async getStreamByName(name: string) { - return this.streamEndpoints.getStreamByName(name) - } - - async createStream(props: StreamProperties) { - return this.streamEndpoints.createStream(props) - } - - async getOrCreateStream(props: { id?: string, name?: string }) { - return this.streamEndpoints.getOrCreateStream(props) - } - - async getStreamPublishers(streamId: string) { - return this.streamEndpoints.getStreamPublishers(streamId) - } - - async isStreamPublisher(streamId: string, ethAddress: string) { - return this.streamEndpoints.isStreamPublisher(streamId, ethAddress) - } - - async getStreamSubscribers(streamId: string) { - return this.streamEndpoints.getStreamSubscribers(streamId) - } - - async isStreamSubscriber(streamId: string, ethAddress: string) { - return this.streamEndpoints.isStreamSubscriber(streamId, ethAddress) - } - - async getStreamValidationInfo(streamId: string) { - return this.streamEndpoints.getStreamValidationInfo(streamId) - } - - async getStreamLast(streamObjectOrId: Stream|string) { - return this.streamEndpoints.getStreamLast(streamObjectOrId) - } - - async getStreamPartsByStorageNode(address: string) { - return this.streamEndpoints.getStreamPartsByStorageNode(address) - } - - async publishHttp(streamObjectOrId: Stream|string, data: Todo, requestOptions: Todo = {}, keepAlive: boolean = true) { - return this.streamEndpoints.publishHttp(streamObjectOrId, data, requestOptions, keepAlive) - } - - async getUserInfo() { - return this.loginEndpoints.getUserInfo() - } - - async getTokenBalance(address: string) { - return this.dataUnionEndpoints.getTokenBalance(address) - } - getDataUnion(contractAddress: string) { return new DataUnion(contractAddress, undefined, this.dataUnionEndpoints) } async deployDataUnion(options?: DataUnionDeployOptions) { - const contract = await this.dataUnionEndpoints.deployDataUnion(options) + const contract = await this.dataUnionEndpoints.deployDataUnionContract(options) return new DataUnion(contract.address, contract.sidechain.address, this.dataUnionEndpoints) } @@ -457,4 +397,10 @@ export default class StreamrClient extends EventEmitter { const contractAddress = this.dataUnionEndpoints.calculateDataUnionMainnetAddress(dataUnionName, deployerAddress) return this.getDataUnion(contractAddress) } + + static generateEthereumAccount() { + return StreamrEthereum.generateEthereumAccount() + } } + +export default StreamrClient diff --git a/src/rest/DataUnionEndpoints.ts b/src/rest/DataUnionEndpoints.ts index f2205677e..b9d76a39c 100644 --- a/src/rest/DataUnionEndpoints.ts +++ b/src/rest/DataUnionEndpoints.ts @@ -594,7 +594,7 @@ export class DataUnionEndpoints { * This triggers DataUnionSidechain contract creation in sidechain, over the bridge (AMB) * @return that resolves when the new DU is deployed over the bridge to side-chain */ - async deployDataUnion(options: DataUnionDeployOptions = {}): Promise { + async deployDataUnionContract(options: DataUnionDeployOptions = {}): Promise { const { owner, joinPartAgents, From a8bd4c6e295e72b1e15c9d6a4d0f62fc4395a189 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 18 Feb 2021 10:20:31 -0500 Subject: [PATCH 474/517] Add npm run watch. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 085914a4b..8d07eef30 100644 --- a/package.json +++ b/package.json @@ -23,7 +23,7 @@ "benchmark": "npm run benchmarks", "prepack": "npm run build", "prebuild": "npm run eslint -- --cache", - "dev": "webpack --progress --colors --watch --mode=development", + "watch": "webpack --progress --watch --mode=development", "eslint": "eslint --cache-location=node_modules/.cache/.eslintcache/ '*/**/*.{js,ts}'", "test": "jest --detectOpenHandles", "test-unit": "jest test/unit --detectOpenHandles", From a9d6eec3ac58c5470300ae7ece3fc9082e5597ec Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 18 Feb 2021 10:31:03 -0500 Subject: [PATCH 475/517] Fix shape of default exported lib for browser. --- webpack.config.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/webpack.config.js b/webpack.config.js index 076d340ce..62fc18c5d 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -111,6 +111,20 @@ module.exports = (env, argv) => { output: { libraryTarget: 'umd2', filename: libraryName + '.web.js', + // NOTE: + // exporting the class directly + // `export default class StreamrClient {}` + // becomes: + // `window.StreamrClient === StreamrClient` + // which is correct, but if we define the class and export separately, + // which is required if we do interface StreamrClient extends …: + // `class StreamrClient {}; export default StreamrClient;` + // becomes: + // `window.StreamrClient = { default: StreamrClient, … }` + // which is wrong for browser builds. + // see: https://github.com/webpack/webpack/issues/706#issuecomment-438007763 + libraryExport: 'default', // This fixes the above. + library: 'StreamrClient', }, node: { stream: true, From 9712ec25d3ee5e5c19998d41b2ba4090a73ddfe1 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 18 Feb 2021 13:48:37 -0500 Subject: [PATCH 476/517] Add npm run check-types, add ts-toolbelt. --- package-lock.json | 5 +++++ package.json | 2 ++ src/StreamrClient.ts | 6 +----- src/types.ts | 4 ++++ 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index 47bd4c852..62fdf3e86 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13736,6 +13736,11 @@ "punycode": "^2.1.1" } }, + "ts-toolbelt": { + "version": "9.3.12", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.3.12.tgz", + "integrity": "sha512-xxvVS/vhnuiBnOplvkKQe4Npp+KvClBCf62v3LqEOMzcOL/6V8eEIqhNHm+dJQq5Obvx6e87eHe56yapW73xSQ==" + }, "tsconfig-paths": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", diff --git a/package.json b/package.json index 8d07eef30..2d16711ae 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "scripts": { "build": "rm -rf dist; NODE_ENV=production webpack --mode=production --progress && npm run build:types", "build:types": "tsc --emitDeclarationOnly", + "check-types": "tsc --noEmit --project ./tsconfig.json", "benchmarks": "node test/benchmarks/publish.js && node test/benchmarks/subscribe.js", "prebuild-benchmark": "npm run build -- --config-name=node-lib", "build-benchmark": "npm run benchmarks", @@ -117,6 +118,7 @@ "quick-lru": "^5.1.1", "readable-stream": "^3.6.0", "streamr-client-protocol": "^8.0.0-beta.2", + "ts-toolbelt": "^9.3.12", "typescript": "^4.1.4", "uuid": "^8.3.2", "webpack-node-externals": "^2.5.2", diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index 5b05258ac..6d20b9573 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -11,16 +11,12 @@ import Connection, { ConnectionError } from './Connection' import Publisher from './publish' import Subscriber from './subscribe' import { getUserId } from './user' -import { Todo } from './types' +import { Todo, MaybeAsync } from './types' import { StreamEndpoints } from './rest/StreamEndpoints' import { LoginEndpoints } from './rest/LoginEndpoints' import { DataUnionEndpoints } from './rest/DataUnionEndpoints' import { DataUnion, DataUnionDeployOptions } from './dataunion/DataUnion' -type Function = (...args: any[]) => any // Utility Type: generic function -type Promisify = (...args: Parameters) => Promise> // Utility Type: make a function async -type MaybeAsync = F | Promisify // Utility Type: make a function maybe async - // TODO get metadata type from streamr-protocol-js project (it doesn't export the type definitions yet) export type OnMessageCallback = MaybeAsync<(message: any, metadata: any) => void> diff --git a/src/types.ts b/src/types.ts index a90812a10..3daaca418 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1 +1,5 @@ +import { F } from 'ts-toolbelt' + +export type MaybeAsync = T | F.Promisify // Utility Type: make a function maybe async + export type Todo = any From faf358c16f567fbf282eea9cc3420085ef913e92 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 18 Feb 2021 13:49:42 -0500 Subject: [PATCH 477/517] Convert Scaffold.js to Scaffold.ts. --- src/utils/{Scaffold.js => Scaffold.ts} | 75 +++++++++++++++++--------- 1 file changed, 50 insertions(+), 25 deletions(-) rename src/utils/{Scaffold.js => Scaffold.ts} (67%) diff --git a/src/utils/Scaffold.js b/src/utils/Scaffold.ts similarity index 67% rename from src/utils/Scaffold.js rename to src/utils/Scaffold.ts index bf40f8b84..1adc7ae16 100644 --- a/src/utils/Scaffold.js +++ b/src/utils/Scaffold.ts @@ -1,5 +1,6 @@ import pLimit from 'p-limit' +import { MaybeAsync } from '../types' import AggregatedError from './AggregatedError' /** @@ -12,20 +13,45 @@ import AggregatedError from './AggregatedError' * onError fires when something errors. Rethrow in onError to keep error, don't rethrow to suppress. * returns a function which should be called whenever something changes that could affect the check. */ +type Step = StepUp | MaybeAsync<() => void> // possibly no StepDown +type StepUp = MaybeAsync<() => StepDown> +type StepDown = MaybeAsync<() => void> + +type ScaffoldOptions = { + onError?: (error: Error) => void + onDone?: MaybeAsync<(shouldUp: boolean, error?: Error) => void> + onChange?: MaybeAsync<(shouldUp: boolean) => void> +} + +const noop = () => {} -export default function Scaffold(sequence = [], _checkFn, { onError, onDone, onChange } = {}) { - let error +export default function Scaffold( + sequence: Step[] = [], + _checkFn: () => Promise, + { onError, onDone, onChange }: ScaffoldOptions = {} +) { + let error: Error | undefined // ignore error if check fails - const nextSteps = sequence.slice().reverse() - const prevSteps = [] - const onDownSteps = [] + const nextSteps: StepUp[] = sequence.slice().reverse().map((fn) => ( + async () => { + const downFn = await fn() + return ( + typeof downFn === 'function' + ? downFn + : noop + ) + } + )) + + const prevSteps: StepUp[] = [] + const onDownSteps: StepDown[] = [] const queue = pLimit(1) let isDone = false let didStart = false - function collectErrors(err) { + function collectErrors(err: Error) { try { if (typeof onError === 'function') { onError(err) // give option to suppress error @@ -37,11 +63,12 @@ export default function Scaffold(sequence = [], _checkFn, { onError, onDone, onC } } - const checkFn = async (...args) => { + const checkShouldUp = async () => { + if (error) { return false } try { - return await _checkFn(...args) + return await _checkFn() } catch (err) { - collectErrors(err, 'in check') + collectErrors(err) } return false } @@ -50,9 +77,7 @@ export default function Scaffold(sequence = [], _checkFn, { onError, onDone, onC let prevShouldUp = false const innerQueue = pLimit(1) - const checkShouldUp = async () => !!(!error && await checkFn()) - - async function next(...args) { + async function next(): Promise { shouldUp = await checkShouldUp() const didChange = prevShouldUp !== shouldUp prevShouldUp = shouldUp @@ -62,7 +87,7 @@ export default function Scaffold(sequence = [], _checkFn, { onError, onDone, onC } catch (err) { collectErrors(err) } - return next(...args) + return next() } if (shouldUp) { @@ -70,27 +95,27 @@ export default function Scaffold(sequence = [], _checkFn, { onError, onDone, onC isDone = false didStart = true let onDownStep - const stepFn = nextSteps.pop() + const stepFn = nextSteps.pop() as StepUp prevSteps.push(stepFn) try { - onDownStep = await stepFn(...args) + onDownStep = await stepFn() } catch (err) { collectErrors(err) } onDownSteps.push(onDownStep || (() => {})) - return next(...args) + return next() } } else if (onDownSteps.length) { isDone = false didStart = true - const stepFn = onDownSteps.pop() + const stepFn = onDownSteps.pop() as StepDown // exists because checked onDownSteps.length try { await stepFn() } catch (err) { collectErrors(err) } - nextSteps.push(prevSteps.pop()) - return next(...args) + nextSteps.push(prevSteps.pop() as StepUp) + return next() } else if (error) { const err = error // eslint-disable-next-line require-atomic-updates @@ -113,15 +138,15 @@ export default function Scaffold(sequence = [], _checkFn, { onError, onDone, onC ) } - const nextDone = async (...args) => { - await innerQueue(() => next(...args)) + const nextDone = async () => { + await innerQueue(() => next()) } - let currentStep - const queuedNext = async (...args) => { + let currentStep: Promise + const queuedNext = async () => { let stepErr try { - currentStep = queue(() => nextDone(...args)) + currentStep = queue(() => nextDone()) await currentStep } catch (err) { stepErr = err @@ -150,7 +175,7 @@ export default function Scaffold(sequence = [], _checkFn, { onError, onDone, onC get pendingCount() { return queue.pendingCount }, - setError(err) { + setError(err: Error) { error = AggregatedError.from(error, err) }, getError() { From c69b33c76a20d15b8c9a8b5fe2421215ca1f3015 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 18 Feb 2021 14:04:23 -0500 Subject: [PATCH 478/517] Convert AggregatedError.js to AggregatedError.ts. --- ...{AggregatedError.js => AggregatedError.ts} | 47 +++++++++++-------- 1 file changed, 28 insertions(+), 19 deletions(-) rename src/utils/{AggregatedError.js => AggregatedError.ts} (57%) diff --git a/src/utils/AggregatedError.js b/src/utils/AggregatedError.ts similarity index 57% rename from src/utils/AggregatedError.js rename to src/utils/AggregatedError.ts index 3da55c1c8..735f9d6da 100644 --- a/src/utils/AggregatedError.js +++ b/src/utils/AggregatedError.ts @@ -2,13 +2,14 @@ * An Error of Errors * Pass an array of errors + message to create * Single error without throwing away other errors + * Specifically not using AggregateError name as this has slightly different API * * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError */ export default class AggregatedError extends Error { - // specifically not using AggregateError name as this has slightly different API - constructor(errors = [], errorMessage = '') { + errors: Set + constructor(errors: Error[] = [], errorMessage = '') { super(errorMessage) this.errors = new Set(errors) if (Error.captureStackTrace) { @@ -16,6 +17,9 @@ export default class AggregatedError extends Error { } } + /** + * Combine any errors from Promise.allSettled into AggregatedError. + */ static fromAllSettled(results = [], errorMessage = '') { const errs = results.map(({ reason }) => reason).filter(Boolean) if (!errs.length) { @@ -25,7 +29,10 @@ export default class AggregatedError extends Error { return new AggregatedError(errs, errorMessage) } - static throwAllSettled(results, errorMessage = '') { + /** + * Combine any errors from Promise.allSettled into AggregatedError and throw it. + */ + static throwAllSettled(results = [], errorMessage = '') { const err = this.fromAllSettled(results, errorMessage) if (err) { throw err @@ -35,33 +42,35 @@ export default class AggregatedError extends Error { /** * Handles 'upgrading' an existing error to an AggregatedError when necesary. */ - - static from(oldErr, newErr, msg) { + static from(oldErr?: Error | AggregatedError, newErr?: Error, msg?: string) { if (newErr && msg) { // copy message newErr.message = `${msg}: ${newErr.message}` // eslint-disable-line no-param-reassign } - switch (true) { - // When no oldErr, just return newErr - case !oldErr: { - return newErr - } - // When oldErr is an AggregatedError, extend it - case typeof oldErr.extend === 'function': { - return oldErr.extend(newErr, msg || newErr.message) - } - // Otherwise create new AggregatedError from oldErr and newErr - default: { - return new AggregatedError([oldErr, newErr], msg || newErr.message) - } + + if (!newErr) { + return oldErr } + + // When no oldErr, just return newErr + if (!oldErr) { + return newErr + } + + // When oldErr is an AggregatedError, extend it + if (oldErr instanceof AggregatedError) { + return oldErr.extend(newErr, msg || newErr.message) + } + + // Otherwise create new AggregatedError from oldErr and newErr + return new AggregatedError([oldErr, newErr], msg || newErr.message) } /** * Create a new error that adds err to list of errors */ - extend(err, message = '') { + extend(err: Error, message = ''): AggregatedError { if (err === this || this.errors.has(err)) { return this } From e1437dc282affdafb77068e8a01cb05cca2d180e Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 18 Feb 2021 16:49:04 -0500 Subject: [PATCH 479/517] Convert util/index.js to util/index.ts. --- .eslintrc.js | 3 +- src/Config.ts | 35 ++++++--- src/StreamrClient.ts | 4 +- src/stream/index.ts | 2 +- src/utils/{index.js => index.ts} | 127 ++++++++++++++++++------------- tsconfig.json | 13 +++- 6 files changed, 116 insertions(+), 68 deletions(-) rename src/utils/{index.js => index.ts} (74%) diff --git a/.eslintrc.js b/.eslintrc.js index 57180859f..155803fe1 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -39,12 +39,11 @@ module.exports = { 'padded-blocks': 'off', 'no-use-before-define': 'off', 'import/order': 'off', + 'no-undef': 'off', 'no-shadow': 'off', '@typescript-eslint/no-shadow': 'error', 'no-unused-vars': 'off', '@typescript-eslint/no-unused-vars': ['error'], - 'no-else-return': 'off', - 'no-return-await': 'off' }, settings: { 'import/resolver': { diff --git a/src/Config.ts b/src/Config.ts index 07a567e65..eeb9773d9 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -3,11 +3,11 @@ import Debug from 'debug' import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import { ExternalProvider, JsonRpcFetchFunc } from '@ethersproject/providers' import { BigNumber } from '@ethersproject/bignumber' - +import { O } from 'ts-toolbelt' import { getVersionString, counterId } from './utils' import { Todo } from './types' -export interface StreamrClientOptions { +export type StreamrClientOptions = { id?: string debug?: Debug.Debugger, auth?: { @@ -49,13 +49,14 @@ export interface StreamrClientOptions { } } +export type StreamrClientConfig = O.Compulsory const { ControlMessage } = ControlLayer const { StreamMessage } = MessageLayer export default function ClientConfig(opts: Partial = {}) { const { id = counterId('StreamrClient') } = opts - const options: StreamrClientOptions = { + const defaults = { debug: Debug(id), // Authentication: identity used by this StreamrClient instance auth: {}, // can contain member privateKey or (window.)ethereum @@ -83,23 +84,30 @@ export default function ClientConfig(opts: Partial = {}) { // Ethereum and Data Union related options // For ethers.js provider params, see https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#provider - mainnet: null, // Default to ethers.js default provider settings + mainnet: undefined, // Default to ethers.js default provider settings sidechain: { - // @ts-expect-error - url: null, // TODO: add our default public service sidechain node, also find good PoA params below + url: undefined, // TODO: add our default public service sidechain node, also find good PoA params below // timeout: // pollingInterval: }, tokenAddress: '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', minimumWithdrawTokenWei: '1000000', // Threshold value set in AMB configs, smallest token amount to pass over the bridge - factoryMainnetAddress: 'TODO', // TODO define this value when we know it // Data Union factory that creates a new Data Union - factorySidechainAddress: 'TODO', // TODO define this value when we know it + sidechainTokenAddress: undefined, // TODO // sidechain token + factoryMainnetAddress: undefined, // TODO // Data Union factory that creates a new Data Union + sidechainAmbAddress: undefined, // Arbitrary Message-passing Bridge (AMB), see https://github.com/poanetwork/tokenbridge payForSignatureTransport: true, // someone must pay for transporting the withdraw tx to mainnet, either us or bridge operator - ...opts, cache: { maxSize: 10000, maxAge: 30 * 60 * 1000, // 30 minutes + } + } + + const options: StreamrClientConfig = { + ...defaults, + ...opts, + cache: { ...opts.cache, + ...defaults.cache, } } @@ -135,8 +143,13 @@ export default function ClientConfig(opts: Partial = {}) { options.auth.apiKey = options.apiKey } - if (options.auth!.privateKey && !options.auth!.privateKey.startsWith('0x')) { - options.auth!.privateKey = `0x${options.auth!.privateKey}` + options.auth = options.auth || {} + + if ('privateKey' in options.auth) { + const { privateKey } = options.auth + if (typeof privateKey === 'string' && privateKey.startsWith('0x')) { + options.auth.privateKey = `0x${options.auth!.privateKey}` + } } return options diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index 6d20b9573..6acf923fd 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -4,7 +4,7 @@ import Debug from 'debug' import { counterId, uuid, CacheAsyncFn } from './utils' import { validateOptions } from './stream/utils' -import Config, { StreamrClientOptions } from './Config' +import Config, { StreamrClientOptions, StreamrClientConfig } from './Config' import StreamrEthereum from './Ethereum' import Session from './Session' import Connection, { ConnectionError } from './Connection' @@ -143,7 +143,7 @@ interface StreamrClient extends StreamEndpoints, LoginEndpoints, DataUnionEndpoi class StreamrClient extends EventEmitter { id: string debug: Debug.Debugger - options: StreamrClientOptions + options: StreamrClientConfig session: Session connection: StreamrConnection publisher: Todo diff --git a/src/stream/index.ts b/src/stream/index.ts index 29245468a..e8f03fe1f 100644 --- a/src/stream/index.ts +++ b/src/stream/index.ts @@ -144,7 +144,7 @@ export default class Stream { async revokePermission(permissionId: number) { return authFetch( - getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'permissions', permissionId), + getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'permissions', String(permissionId)), this._client.session, { method: 'DELETE', diff --git a/src/utils/index.js b/src/utils/index.ts similarity index 74% rename from src/utils/index.js rename to src/utils/index.ts index f0cff05e9..9d4e06c46 100644 --- a/src/utils/index.js +++ b/src/utils/index.ts @@ -1,4 +1,5 @@ import { inspect } from 'util' +import EventEmitter from 'events' import { v4 as uuidv4 } from 'uuid' import uniqueId from 'lodash.uniqueid' @@ -6,8 +7,10 @@ import LRU from 'quick-lru' import pMemoize from 'p-memoize' import pLimit from 'p-limit' import mem from 'mem' +import { L, F } from 'ts-toolbelt' import pkg from '../../package.json' +import { MaybeAsync } from '../types' import AggregatedError from './AggregatedError' import Scaffold from './Scaffold' @@ -44,7 +47,7 @@ export function randomString(length = 20) { export const counterId = (() => { const MAX_PREFIXES = 256 - let counts = {} // possible we could switch this to WeakMap and pass functions or classes. + let counts: { [prefix: string]: number } = {} // possible we could switch this to WeakMap and pass functions or classes. let didWarn = false const counterIdFn = (prefix = 'ID') => { // pedantic: wrap around if count grows too large @@ -67,7 +70,7 @@ export const counterId = (() => { * * @param {string?} prefix */ - counterIdFn.clear = (...args) => { + counterIdFn.clear = (...args: [string] | []) => { // check length to differentiate between clear(undefined) & clear() if (args.length) { const [prefix] = args @@ -90,10 +93,10 @@ export function getVersionString() { * Rejects if an 'error' event is received before resolving. */ -export function waitFor(emitter, event) { +export function waitFor(emitter: EventEmitter, event: Parameters[0]) { return new Promise((resolve, reject) => { - let onError - const onEvent = (value) => { + let onError: (error: Error) => void + const onEvent = (value: any) => { emitter.off('error', onError) resolve(value) } @@ -107,11 +110,16 @@ export function waitFor(emitter, event) { }) } -export const getEndpointUrl = (baseUrl, ...pathParts) => { +export const getEndpointUrl = (baseUrl: string, ...pathParts: string[]) => { return baseUrl + '/' + pathParts.map((part) => encodeURIComponent(part)).join('/') } -function clearMatching(cache, matchFn) { +type Collection = { + keys: Map['keys'] + delete: Map['delete'] +} + +function clearMatching(cache: Collection, matchFn: (key: unknown) => boolean) { for (const key of cache.keys()) { if (matchFn(key)) { cache.delete(key) @@ -135,28 +143,29 @@ function clearMatching(cache, matchFn) { * ``` */ -export function CacheAsyncFn(asyncFn, { +export function CacheAsyncFn(asyncFn: Parameters[0], { maxSize = 10000, maxAge = 30 * 60 * 1000, // 30 minutes cachePromiseRejection = false, - onEviction, + onEviction = () => {}, ...opts } = {}) { - const cache = new LRU({ + const cache = new LRU({ maxSize, maxAge, onEviction, }) - const cachedFn = pMemoize(asyncFn, { + const cachedFn = Object.assign(pMemoize(asyncFn, { maxAge, cachePromiseRejection, cache, ...opts, + }), { + clear: () => pMemoize.clear(cachedFn), + clearMatching: (...args: L.Tail>) => clearMatching(cache, ...args), }) - cachedFn.clear = () => pMemoize.clear(cachedFn) - cachedFn.clearMatching = (...args) => clearMatching(cache, ...args) return cachedFn } @@ -174,24 +183,27 @@ export function CacheAsyncFn(asyncFn, { * ``` */ -export function CacheFn(fn, { +export function CacheFn(fn: Parameters[0], { maxSize = 10000, maxAge = 30 * 60 * 1000, // 30 minutes - onEviction, + onEviction = () => {}, ...opts } = {}) { - const cache = new LRU({ + const cache = new LRU({ maxSize, maxAge, onEviction, }) - const cachedFn = mem(fn, { + + const cachedFn = Object.assign(mem(fn, { maxAge, cache, - ...opts + ...opts, + }), { + clear: () => mem.clear(cachedFn), + clearMatching: (...args: L.Tail>) => clearMatching(cache, ...args), }) - cachedFn.clear = () => mem.clear(cachedFn) - cachedFn.clearMatching = (...args) => clearMatching(cache, ...args) + return cachedFn } @@ -204,10 +216,12 @@ export function CacheFn(fn, { * Also has a wrapError(fn) method that wraps a function to settle this promise if error * Defer optionally takes executor function ala `new Promise(executor)` */ +type PromiseResolve = L.Compulsory['then']>>[0] +type PromiseReject = L.Compulsory['then']>>[1] -export function Defer(executor = () => {}) { - let resolve - let reject +export function Defer(executor: (...args: Parameters['then']>) => void = () => {}) { + let resolve: PromiseResolve = () => {} + let reject: PromiseReject = () => {} // eslint-disable-next-line promise/param-names const p = new Promise((_resolve, _reject) => { resolve = _resolve @@ -215,8 +229,8 @@ export function Defer(executor = () => {}) { executor(resolve, reject) }) - function wrap(fn) { - return async (...args) => { + function wrap(fn: F.Function) { + return async (...args: unknown[]) => { try { return resolve(await fn(...args)) } catch (err) { @@ -226,8 +240,8 @@ export function Defer(executor = () => {}) { } } - function wrapError(fn) { - return async (...args) => { + function wrapError(fn: F.Function) { + return async (...args: unknown[]) => { try { return await fn(...args) } catch (err) { @@ -237,11 +251,11 @@ export function Defer(executor = () => {}) { } } - function handleErrBack(err) { + function handleErrBack(err: Error) { if (err) { reject(err) } else { - resolve() + resolve(undefined) } } @@ -265,10 +279,10 @@ export function Defer(executor = () => {}) { * ``` */ -export function LimitAsyncFnByKey(limit = 1) { +export function LimitAsyncFnByKey(limit = 1) { const pending = new Map() const queueOnEmptyTasks = new Map() - const f = async (id, fn) => { + const f = async (id: KeyType, fn: () => Promise) => { const limitFn = pending.get(id) || pending.set(id, pLimit(limit)).get(id) const onQueueEmpty = queueOnEmptyTasks.get(id) || queueOnEmptyTasks.set(id, Defer()).get(id) try { @@ -285,7 +299,7 @@ export function LimitAsyncFnByKey(limit = 1) { } } - f.getOnQueueEmpty = async (id) => { + f.getOnQueueEmpty = async (id: KeyType) => { return queueOnEmptyTasks.get(id) || pending.set(id, Defer()).get(id) } @@ -303,9 +317,9 @@ export function LimitAsyncFnByKey(limit = 1) { * Execute functions in parallel, but ensure they resolve in the order they were executed */ -export function pOrderedResolve(fn) { +export function pOrderedResolve(fn: F.Function) { const queue = pLimit(1) - return async (...args) => { + return async (...args: Parameters) => { const d = Defer() const done = queue(() => d) // eslint-disable-next-line promise/catch-or-return @@ -318,9 +332,9 @@ export function pOrderedResolve(fn) { * Returns a function that executes with limited concurrency. */ -export function pLimitFn(fn, limit = 1) { +export function pLimitFn(fn: F.Function, limit = 1) { const queue = pLimit(limit) - return (...args) => queue(() => fn(...args)) + return (...args: unknown[]) => queue(() => fn(...args)) } /** @@ -328,9 +342,9 @@ export function pLimitFn(fn, limit = 1) { * Returns same promise while task is executing. */ -export function pOne(fn) { - let inProgress - return (...args) => { +export function pOne(fn: F.Function) { + let inProgress: Promise | undefined + return (...args: Parameters) => { if (!inProgress) { inProgress = Promise.resolve(fn(...args)).finally(() => { inProgress = undefined @@ -342,8 +356,9 @@ export function pOne(fn) { } export class TimeoutError extends Error { - constructor(msg = '', timeout = 0, ...args) { - super(`The operation timed out. ${timeout}ms. ${msg}`, ...args) + timeout: number + constructor(msg = '', timeout = 0) { + super(`The operation timed out. ${timeout}ms. ${msg}`) this.timeout = timeout if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor) @@ -365,8 +380,16 @@ export class TimeoutError extends Error { * message and rejectOnTimeout are optional. */ -export async function pTimeout(promise, ...args) { - let opts = {} +type pTimeoutOpts = { + timeout?: number, + message?: string, + rejectOnTimeout?: boolean, +} + +type pTimeoutArgs = [timeout?: number, message?: string] | [pTimeoutOpts] + +export async function pTimeout(promise: Promise, ...args: pTimeoutArgs) { + let opts: pTimeoutOpts = {} if (args[0] && typeof args[0] === 'object') { [opts] = args } else { @@ -380,7 +403,7 @@ export async function pTimeout(promise, ...args) { } let timedOut = false - let t + let t: ReturnType return Promise.race([ Promise.resolve(promise).catch((err) => { if (timedOut) { @@ -396,7 +419,7 @@ export async function pTimeout(promise, ...args) { if (rejectOnTimeout) { reject(new TimeoutError(message, timeout)) } else { - resolve() + resolve(undefined) } }, timeout) }) @@ -409,18 +432,20 @@ export async function pTimeout(promise, ...args) { * Convert allSettled results into a thrown Aggregate error if necessary. */ -export async function allSettledValues(items, errorMessage = '') { +export async function allSettledValues(items: Parameters, errorMessage = '') { const result = await Promise.allSettled(items) - - const errs = result.filter(({ status }) => status === 'rejected').map(({ reason }) => reason) + const errs = result + .filter(({ status }) => status === 'rejected') + .map((v) => (v as PromiseRejectedResult).reason) if (errs.length) { throw new AggregatedError(errs, errorMessage) } - return result.map(({ value }) => value) + return result + .map((v) => (v as PromiseFulfilledResult).value) } -export async function sleep(ms) { +export async function sleep(ms: number = 0) { return new Promise((resolve) => { setTimeout(resolve, ms) }) @@ -432,7 +457,7 @@ export async function sleep(ms) { * @param {number} [timeOutMs=10000] stop waiting after that many milliseconds, -1 for disable * @param {number} [pollingIntervalMs=100] check condition between so many milliseconds */ -export async function until(condition, timeOutMs = 10000, pollingIntervalMs = 100) { +export async function until(condition: MaybeAsync<() => boolean>, timeOutMs = 10000, pollingIntervalMs = 100) { let timeout = false if (timeOutMs > 0) { setTimeout(() => { timeout = true }, timeOutMs) diff --git a/tsconfig.json b/tsconfig.json index 4a3cef9df..16801cdce 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,11 +1,22 @@ { "compilerOptions": { - "target": "es6", + "target": "ES2015", "module": "commonjs", "allowJs": true, "declaration": true, "declarationDir": "dist/types", "outDir": "dist", + "lib": [ + "ES5", + "ES2015", + "ES2016", + "ES2017", + "ES2018", + "ES2019", + "ES2020", + "ESNext", + "DOM" + ], "strict": true, "esModuleInterop": true, "resolveJsonModule": true, From cc8bcf7c0587f16525b457c2a9ddd3559199923f Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 18 Feb 2021 16:49:35 -0500 Subject: [PATCH 480/517] Update some packages. --- package-lock.json | 2036 +++++++++++++++++++++------------------------ package.json | 7 +- 2 files changed, 947 insertions(+), 1096 deletions(-) diff --git a/package-lock.json b/package-lock.json index 62fdf3e86..d1a2e54a7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "@babel/cli": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.12.10.tgz", - "integrity": "sha512-+y4ZnePpvWs1fc/LhZRTHkTesbXkyBYuOB+5CyodZqrEuETXi3zOVfpAQIdgC3lXbHLTDG9dQosxR9BhvLKDLQ==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.13.0.tgz", + "integrity": "sha512-y5AohgeVhU+wO5kU1WGMLdocFj83xCxVjsVFa2ilII8NEwmBZvx7Ambq621FbFIK68loYJ9p43nfoi6es+rzSA==", "dev": true, "requires": { "@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents", @@ -23,237 +23,268 @@ } }, "@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", "dev": true, "requires": { - "@babel/highlight": "^7.10.4" + "@babel/highlight": "^7.12.13" } }, "@babel/compat-data": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.7.tgz", - "integrity": "sha512-YaxPMGs/XIWtYqrdEOZOCPsVWfEoriXopnsz3/i7apYPXQ3698UFhS6dVT1KN5qOsWmVgw/FOrmQgpRaZayGsw==", + "version": "7.13.6", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.6.tgz", + "integrity": "sha512-VhgqKOWYVm7lQXlvbJnWOzwfAQATd2nV52koT0HZ/LdDH0m4DUDwkKYsH+IwpXb+bKPyBJzawA4I6nBKqZcpQw==", "dev": true }, "@babel/core": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.10.tgz", - "integrity": "sha512-eTAlQKq65zHfkHZV0sIVODCPGVgoo1HdBlbSLi9CqOzuZanMv2ihzY+4paiKr1mH+XmYESMAmJ/dpZ68eN6d8w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.10", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.5", - "@babel/parser": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.10", + "version": "7.13.1", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.1.tgz", + "integrity": "sha512-FzeKfFBG2rmFtGiiMdXZPFt/5R5DXubVi82uYhjGX4Msf+pgYQMCFIqFXZWs5vbIYbf14VeBIgdGI03CDOOM1w==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.0", + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-transforms": "^7.13.0", + "@babel/helpers": "^7.13.0", + "@babel/parser": "^7.13.0", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.0", "convert-source-map": "^1.7.0", "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", + "gensync": "^1.0.0-beta.2", "json5": "^2.1.2", "lodash": "^4.17.19", - "semver": "^5.4.1", + "semver": "7.0.0", "source-map": "^0.5.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } } }, "@babel/generator": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.11.tgz", - "integrity": "sha512-Ggg6WPOJtSi8yYQvLVjG8F/TlpWDlKx0OpS4Kt+xMQPs5OaGYWy+v1A+1TvxI6sAMGZpKWWoAQ1DaeQbImlItA==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.13.0.tgz", + "integrity": "sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw==", "dev": true, "requires": { - "@babel/types": "^7.12.11", + "@babel/types": "^7.13.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.10.tgz", - "integrity": "sha512-XplmVbC1n+KY6jL8/fgLVXXUauDIB+lD5+GsQEh6F6GBF1dq1qy4DP4yXWzDKcoqXB3X58t61e85Fitoww4JVQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", + "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", "dev": true, "requires": { - "@babel/types": "^7.12.10" + "@babel/types": "^7.12.13" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", - "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz", + "integrity": "sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA==", "dev": true, "requires": { - "@babel/helper-explode-assignable-expression": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/helper-explode-assignable-expression": "^7.12.13", + "@babel/types": "^7.12.13" } }, "@babel/helper-compilation-targets": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.5.tgz", - "integrity": "sha512-+qH6NrscMolUlzOYngSBMIOQpKUGPPsc61Bu5W10mg84LxZ7cmvnBHzARKbDoFxVvqqAbj6Tg6N7bSrWSPXMyw==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.0.tgz", + "integrity": "sha512-SOWD0JK9+MMIhTQiUVd4ng8f3NXhPVQvTv7D3UN4wbp/6cAHnB2EmMaU1zZA2Hh1gwme+THBrVSqTFxHczTh0Q==", "dev": true, "requires": { - "@babel/compat-data": "^7.12.5", - "@babel/helper-validator-option": "^7.12.1", + "@babel/compat-data": "^7.13.0", + "@babel/helper-validator-option": "^7.12.17", "browserslist": "^4.14.5", - "semver": "^5.5.0" + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } } }, "@babel/helper-create-class-features-plugin": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", - "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.13.0.tgz", + "integrity": "sha512-twwzhthM4/+6o9766AW2ZBHpIHPSGrPGk1+WfHiu13u/lBnggXGNYCpeAyVfNwGDKfkhEDp+WOD/xafoJ2iLjA==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.12.1", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.10.4" + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-member-expression-to-functions": "^7.13.0", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/helper-replace-supers": "^7.13.0", + "@babel/helper-split-export-declaration": "^7.12.13" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.7.tgz", - "integrity": "sha512-idnutvQPdpbduutvi3JVfEgcVIHooQnhvhx0Nk9isOINOIGYkZea1Pk2JlJRiUnMefrlvr0vkByATBY/mB4vjQ==", + "version": "7.12.17", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.17.tgz", + "integrity": "sha512-p2VGmBu9oefLZ2nQpgnEnG0ZlRPvL8gAGvPUMQwUdaE8k49rOMuZpOwdQoy5qJf6K8jL3bcAMhVUlHAjIgJHUg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", + "@babel/helper-annotate-as-pure": "^7.12.13", "regexpu-core": "^4.7.1" } }, - "@babel/helper-define-map": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", - "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", + "@babel/helper-define-polyfill-provider": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.1.2.tgz", + "integrity": "sha512-hWeolZJivTNGHXHzJjQz/NwDaG4mGXf22ZroOP8bQYgvHNzaQ5tylsVbAcAS2oDjXBwpu8qH2I/654QFS2rDpw==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/types": "^7.10.5", - "lodash": "^4.17.19" + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } } }, "@babel/helper-explode-assignable-expression": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", - "integrity": "sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz", + "integrity": "sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA==", "dev": true, "requires": { - "@babel/types": "^7.12.1" + "@babel/types": "^7.13.0" } }, "@babel/helper-function-name": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.11.tgz", - "integrity": "sha512-AtQKjtYNolKNi6nNNVLQ27CP6D9oFR6bq/HPYSizlzbp7uC1M59XJe8L+0uXjbIaZaUJF99ruHqVGiKXU/7ybA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", + "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.12.10", - "@babel/template": "^7.12.7", - "@babel/types": "^7.12.11" + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.12.13" } }, "@babel/helper-get-function-arity": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.10.tgz", - "integrity": "sha512-mm0n5BPjR06wh9mPQaDdXWDoll/j5UpCAPl1x8fS71GHm7HA6Ua2V4ylG1Ju8lvcTOietbPNNPaSilKj+pj+Ag==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", "dev": true, "requires": { - "@babel/types": "^7.12.10" + "@babel/types": "^7.12.13" } }, "@babel/helper-hoist-variables": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", - "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.0.tgz", + "integrity": "sha512-0kBzvXiIKfsCA0y6cFEIJf4OdzfpRuNk4+YTeHZpGGc666SATFKTz6sRncwFnQk7/ugJ4dSrCj6iJuvW4Qwr2g==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.0" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.7.tgz", - "integrity": "sha512-DCsuPyeWxeHgh1Dus7APn7iza42i/qXqiFPWyBDdOFtvS581JQePsc1F/nD+fHrcswhLlRc2UpYS1NwERxZhHw==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.0.tgz", + "integrity": "sha512-yvRf8Ivk62JwisqV1rFRMxiSMDGnN6KH1/mDMmIrij4jztpQNRoHqqMG3U6apYbGRPJpgPalhva9Yd06HlUxJQ==", "dev": true, "requires": { - "@babel/types": "^7.12.7" + "@babel/types": "^7.13.0" } }, "@babel/helper-module-imports": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.5.tgz", - "integrity": "sha512-SR713Ogqg6++uexFRORf/+nPXMmWIn80TALu0uaFb+iQIUoR7bOC7zBWyzBs5b3tBBJXuyD0cRu1F15GyzjOWA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz", + "integrity": "sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==", "dev": true, "requires": { - "@babel/types": "^7.12.5" + "@babel/types": "^7.12.13" } }, "@babel/helper-module-transforms": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", - "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", - "dev": true, - "requires": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-simple-access": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/helper-validator-identifier": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.0.tgz", + "integrity": "sha512-Ls8/VBwH577+pw7Ku1QkUWIyRRNHpYlts7+qSqBBFCW3I8QteB9DxfcZ5YJpOwH6Ihe/wn8ch7fMGOP1OhEIvw==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-replace-supers": "^7.13.0", + "@babel/helper-simple-access": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/helper-validator-identifier": "^7.12.11", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.0", "lodash": "^4.17.19" } }, "@babel/helper-optimise-call-expression": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.10.tgz", - "integrity": "sha512-4tpbU0SrSTjjt65UMWSrUOPZTsgvPgGG4S8QSTNHacKzpS51IVWGDj0yCwyeZND/i+LSN2g/O63jEXEWm49sYQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", + "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", "dev": true, "requires": { - "@babel/types": "^7.12.10" + "@babel/types": "^7.12.13" } }, "@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", + "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", "dev": true }, "@babel/helper-remap-async-to-generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz", - "integrity": "sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz", + "integrity": "sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-wrap-function": "^7.10.4", - "@babel/types": "^7.12.1" + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-wrap-function": "^7.13.0", + "@babel/types": "^7.13.0" } }, "@babel/helper-replace-supers": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.11.tgz", - "integrity": "sha512-q+w1cqmhL7R0FNzth/PLLp2N+scXEK/L2AHbXUyydxp828F4FEa5WcVoqui9vFRiHDQErj9Zof8azP32uGVTRA==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.0.tgz", + "integrity": "sha512-Segd5me1+Pz+rmN/NFBOplMbZG3SqRJOBlY+mA0SxAv6rjj7zJqr1AVr3SfzUVTLCv7ZLU5FycOM/SBGuLPbZw==", "dev": true, "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.7", - "@babel/helper-optimise-call-expression": "^7.12.10", - "@babel/traverse": "^7.12.10", - "@babel/types": "^7.12.11" + "@babel/helper-member-expression-to-functions": "^7.13.0", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.0" } }, "@babel/helper-simple-access": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", - "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.13.tgz", + "integrity": "sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA==", "dev": true, "requires": { - "@babel/types": "^7.12.1" + "@babel/types": "^7.12.13" } }, "@babel/helper-skip-transparent-expression-wrappers": { @@ -266,12 +297,12 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.11.tgz", - "integrity": "sha512-LsIVN8j48gHgwzfocYUSkO/hjYAOJqlpJEc7tGXcIm4cubjVUf8LGW6eWRyxEu7gA25q02p0rQUWoCI33HNS5g==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", "dev": true, "requires": { - "@babel/types": "^7.12.11" + "@babel/types": "^7.12.13" } }, "@babel/helper-validator-identifier": { @@ -281,182 +312,182 @@ "dev": true }, "@babel/helper-validator-option": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.11.tgz", - "integrity": "sha512-TBFCyj939mFSdeX7U7DDj32WtzYY7fDcalgq8v3fBZMNOJQNn7nOYzMaUCiPxPYfCup69mtIpqlKgMZLvQ8Xhw==", + "version": "7.12.17", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", + "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", "dev": true }, "@babel/helper-wrap-function": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz", - "integrity": "sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz", + "integrity": "sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" + "@babel/helper-function-name": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.0" } }, "@babel/helpers": { - "version": "7.12.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.5.tgz", - "integrity": "sha512-lgKGMQlKqA8meJqKsW6rUnc4MdUk35Ln0ATDqdM1a/UpARODdI4j5Y5lVfUScnSNkJcdCRAaWkspykNoFg9sJA==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.13.0.tgz", + "integrity": "sha512-aan1MeFPxFacZeSz6Ld7YZo5aPuqnKlD7+HZY75xQsueczFccP9A7V05+oe0XpLwHK3oLorPe9eaAUljL7WEaQ==", "dev": true, "requires": { - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.5", - "@babel/types": "^7.12.5" + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.0" } }, "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", + "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.10.4", + "@babel/helper-validator-identifier": "^7.12.11", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.11.tgz", - "integrity": "sha512-N3UxG+uuF4CMYoNj8AhnbAcJF0PiuJ9KHuy1lQmkYsxTer/MAH9UBNHsBoAX/4s6NvlDD047No8mYVGGzLL4hg==", + "version": "7.13.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.4.tgz", + "integrity": "sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==", "dev": true }, "@babel/plugin-proposal-async-generator-functions": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.12.tgz", - "integrity": "sha512-nrz9y0a4xmUrRq51bYkWJIO5SBZyG2ys2qinHsN0zHDHVsUaModrkpyWWWXfGqYQmOL3x9sQIcTNN/pBGpo09A==", + "version": "7.13.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.13.5.tgz", + "integrity": "sha512-8cErJEDzhZgNKzYyjCKsHuyPqtWxG8gc9h4OFSUDJu0vCAOsObPU2LcECnW0kJwh/b+uUz46lObVzIXw0fzAbA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.12.1", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-remap-async-to-generator": "^7.13.0", "@babel/plugin-syntax-async-generators": "^7.8.0" } }, "@babel/plugin-proposal-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", - "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz", + "integrity": "sha512-KnTDjFNC1g+45ka0myZNvSBFLhNCLN+GeGYLDEA8Oq7MZ6yMgfLoIRh86GRT0FjtJhZw8JyUskP9uvj5pHM9Zg==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-class-features-plugin": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", - "integrity": "sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==", + "version": "7.12.17", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.17.tgz", + "integrity": "sha512-ZNGoFZqrnuy9H2izB2jLlnNDAfVPlGl5NhFEiFe4D84ix9GQGygF+CWMGHKuE+bpyS/AOuDQCnkiRNqW2IzS1Q==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-dynamic-import": "^7.8.0" } }, "@babel/plugin-proposal-export-namespace-from": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz", - "integrity": "sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.13.tgz", + "integrity": "sha512-INAgtFo4OnLN3Y/j0VwAgw3HDXcDtX+C/erMvWzuV9v71r7urb6iyMXu7eM9IgLr1ElLlOkaHjJ0SbCmdOQ3Iw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, "@babel/plugin-proposal-json-strings": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz", - "integrity": "sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.13.tgz", + "integrity": "sha512-v9eEi4GiORDg8x+Dmi5r8ibOe0VXoKDeNPYcTTxdGN4eOWikrJfDJCJrr1l5gKGvsNyGJbrfMftC2dTL6oz7pg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-json-strings": "^7.8.0" } }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz", - "integrity": "sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.13.tgz", + "integrity": "sha512-fqmiD3Lz7jVdK6kabeSr1PZlWSUVqSitmHEe3Z00dtGTKieWnX9beafvavc32kjORa5Bai4QNHgFDwWJP+WtSQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz", - "integrity": "sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.13.0.tgz", + "integrity": "sha512-UkAvFA/9+lBBL015gjA68NvKiCReNxqFLm3SdNKaM3XXoDisA7tMAIX4PmIwatFoFqMxxT3WyG9sK3MO0Kting==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.13.0", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.7.tgz", - "integrity": "sha512-8c+uy0qmnRTeukiGsjLGy6uVs/TFjJchGXUeBqlG4VWYOdJWkhhVPdQ3uHwbmalfJwv2JsV0qffXP4asRfL2SQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.13.tgz", + "integrity": "sha512-O1jFia9R8BUCl3ZGB7eitaAPu62TXJRHn7rh+ojNERCFyqRwJMTmhz+tJ+k0CwI6CLjX/ee4qW74FSqlq9I35w==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-object-rest-spread": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", - "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.13.0.tgz", + "integrity": "sha512-B4qphdSTp0nLsWcuei07JPKeZej4+Hd22MdnulJXQa1nCcGSBlk8FiqenGERaPZ+PuYhz4Li2Wjc8yfJvHgUMw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.13.0", "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.12.1" + "@babel/plugin-transform-parameters": "^7.13.0" } }, "@babel/plugin-proposal-optional-catch-binding": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", - "integrity": "sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.13.tgz", + "integrity": "sha512-9+MIm6msl9sHWg58NvqpNpLtuFbmpFYk37x8kgnGzAHvX35E1FyAwSUt5hIkSoWJFSAH+iwU8bJ4fcD1zKXOzg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.12.13", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.7.tgz", - "integrity": "sha512-4ovylXZ0PWmwoOvhU2vhnzVNnm88/Sm9nx7V8BPgMvAzn5zDou3/Awy0EjglyubVHasJj+XCEkr/r1X3P5elCA==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.13.0.tgz", + "integrity": "sha512-OVRQOZEBP2luZrvEbNSX5FfWDousthhdEoAOpej+Tpe58HFLvqRClT89RauIvBuCDFEip7GW1eT86/5lMy2RNA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.13.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", "@babel/plugin-syntax-optional-chaining": "^7.8.0" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", - "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz", + "integrity": "sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-class-features-plugin": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-proposal-unicode-property-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", - "integrity": "sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz", + "integrity": "sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-syntax-async-generators": { @@ -478,12 +509,12 @@ } }, "@babel/plugin-syntax-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", - "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-syntax-dynamic-import": { @@ -577,12 +608,12 @@ } }, "@babel/plugin-syntax-top-level-await": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", - "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", + "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-syntax-typescript": { @@ -592,550 +623,378 @@ "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.12.13" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", - "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", - "dev": true - } } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", - "integrity": "sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz", + "integrity": "sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz", - "integrity": "sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz", + "integrity": "sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg==", "dev": true, "requires": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.12.1" + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-remap-async-to-generator": "^7.13.0" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz", - "integrity": "sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz", + "integrity": "sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.12.tgz", - "integrity": "sha512-VOEPQ/ExOVqbukuP7BYJtI5ZxxsmegTwzZ04j1aF0dkSypGo9XpDHuOrABsJu+ie+penpSJheDJ11x1BEZNiyQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.13.tgz", + "integrity": "sha512-Pxwe0iqWJX4fOOM2kEZeUuAxHMWb9nK+9oh5d11bsLoB0xMg+mkDpt0eYuDZB7ETrY9bbcVlKUGTOGWy7BHsMQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-classes": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz", - "integrity": "sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.13.0.tgz", + "integrity": "sha512-9BtHCPUARyVH1oXGcSJD3YpsqRLROJx5ZNP6tN5vnk17N0SVf9WCtf8Nuh1CFmgByKKAIMstitKduoCmsaDK5g==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-define-map": "^7.10.4", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.10.4", + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-replace-supers": "^7.13.0", + "@babel/helper-split-export-declaration": "^7.12.13", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", - "integrity": "sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz", + "integrity": "sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-destructuring": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz", - "integrity": "sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.13.0.tgz", + "integrity": "sha512-zym5em7tePoNT9s964c0/KU3JPPnuq7VhIxPRefJ4/s82cD+q1mgKfuGRDMCPL0HTyKz4dISuQlCusfgCJ86HA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz", - "integrity": "sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz", + "integrity": "sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz", - "integrity": "sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz", + "integrity": "sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz", - "integrity": "sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz", + "integrity": "sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA==", "dev": true, "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-for-of": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz", - "integrity": "sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz", + "integrity": "sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-function-name": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz", - "integrity": "sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz", + "integrity": "sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ==", "dev": true, "requires": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", - "integrity": "sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz", + "integrity": "sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz", - "integrity": "sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz", + "integrity": "sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", - "integrity": "sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.13.0.tgz", + "integrity": "sha512-EKy/E2NHhY/6Vw5d1k3rgoobftcNUmp9fGjb9XZwQLtTctsRBOTRO7RHHxfIky1ogMN5BxN7p9uMA3SzPfotMQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-module-transforms": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz", - "integrity": "sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.13.0.tgz", + "integrity": "sha512-j7397PkIB4lcn25U2dClK6VLC6pr2s3q+wbE8R3vJvY6U1UTBBj0n6F+5v6+Fd/UwfDPAorMOs2TV+T4M+owpQ==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-simple-access": "^7.12.1", + "@babel/helper-module-transforms": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-simple-access": "^7.12.13", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz", - "integrity": "sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.13.tgz", + "integrity": "sha512-aHfVjhZ8QekaNF/5aNdStCGzwTbU7SI5hUybBKlMzqIMC7w7Ho8hx5a4R/DkTHfRfLwHGGxSpFt9BfxKCoXKoA==", "dev": true, "requires": { - "@babel/helper-hoist-variables": "^7.10.4", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-validator-identifier": "^7.10.4", + "@babel/helper-hoist-variables": "^7.12.13", + "@babel/helper-module-transforms": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-validator-identifier": "^7.12.11", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz", - "integrity": "sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.13.0.tgz", + "integrity": "sha512-D/ILzAh6uyvkWjKKyFE/W0FzWwasv6vPTSqPcjxFqn6QpX3u8DjRVliq4F2BamO2Wee/om06Vyy+vPkNrd4wxw==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-module-transforms": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz", - "integrity": "sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz", + "integrity": "sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1" + "@babel/helper-create-regexp-features-plugin": "^7.12.13" } }, "@babel/plugin-transform-new-target": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", - "integrity": "sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz", + "integrity": "sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-object-super": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz", - "integrity": "sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz", + "integrity": "sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1" + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-replace-supers": "^7.12.13" } }, "@babel/plugin-transform-parameters": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", - "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.13.0.tgz", + "integrity": "sha512-Jt8k/h/mIwE2JFEOb3lURoY5C85ETcYPnbuAJ96zRBzh1XHtQZfs62ChZ6EP22QlC8c7Xqr9q+e1SU5qttwwjw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-property-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz", - "integrity": "sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz", + "integrity": "sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-regenerator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", - "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.13.tgz", + "integrity": "sha512-lxb2ZAvSLyJ2PEe47hoGWPmW22v7CtSl9jW8mingV4H2sEX/JOcrAj2nPuGWi56ERUm2bUpjKzONAuT6HCn2EA==", "dev": true, "requires": { "regenerator-transform": "^0.14.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", - "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz", + "integrity": "sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-runtime": { - "version": "7.12.17", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.12.17.tgz", - "integrity": "sha512-s+kIJxnaTj+E9Q3XxQZ5jOo+xcogSe3V78/iFQ5RmoT0jROdpcdxhfGdq/VLqW1hFSzw6VjqN8aQqTaAMixWsw==", + "version": "7.13.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.6.tgz", + "integrity": "sha512-QsTomUTIeOdYrNsOMJRSp2QzGvB1KYD4ePCC8Mei2SuoHScncYS3h1E9PR5gDL7buJmcqIHrWyH6B5GZMgDrRg==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13", - "semver": "^5.5.1" + "@babel/helper-plugin-utils": "^7.13.0", + "babel-plugin-polyfill-corejs2": "^0.1.4", + "babel-plugin-polyfill-corejs3": "^0.1.3", + "babel-plugin-polyfill-regenerator": "^0.1.2", + "semver": "7.0.0" }, "dependencies": { - "@babel/helper-module-imports": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz", - "integrity": "sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", - "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", "dev": true } } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", - "integrity": "sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz", + "integrity": "sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-spread": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz", - "integrity": "sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz", + "integrity": "sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4", + "@babel/helper-plugin-utils": "^7.13.0", "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.7.tgz", - "integrity": "sha512-VEiqZL5N/QvDbdjfYQBhruN0HYjSPjC4XkeqW4ny/jNtH9gcbgaqBIXYEZCNnESMAGs0/K/R7oFGMhOyu/eIxg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz", + "integrity": "sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-template-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz", - "integrity": "sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz", + "integrity": "sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.13.0" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.12.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.10.tgz", - "integrity": "sha512-JQ6H8Rnsogh//ijxspCjc21YPd3VLVoYtAwv3zQmqAt8YGYUtdo5usNhdl4b9/Vir2kPFZl6n1h0PfUz4hJhaA==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz", + "integrity": "sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-typescript": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.12.13.tgz", - "integrity": "sha512-z1VWskPJxK9tfxoYvePWvzSJC+4pxXr8ArmRm5ofqgi+mwpKg6lvtomkIngBYMJVnKhsFYVysCQLDn//v2RHcg==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.13.0.tgz", + "integrity": "sha512-elQEwluzaU8R8dbVuW2Q2Y8Nznf7hnjM7+DSCd14Lo5fF63C9qNLbwZYbmZrtV9/ySpSUpkRpQXvJb6xyu4hCQ==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.12.13", - "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-create-class-features-plugin": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0", "@babel/plugin-syntax-typescript": "^7.12.13" - }, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "dev": true, - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/generator": { - "version": "7.12.15", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.15.tgz", - "integrity": "sha512-6F2xHxBiFXWNSGb7vyCUTBF8RCLY66rS0zEPcP8t/nQyXjha5EuK4z7H5o7fWG8B4M7y6mqVWq1J+1PuwRhecQ==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.13.tgz", - "integrity": "sha512-Vs/e9wv7rakKYeywsmEBSRC9KtmE7Px+YBlESekLeJOF0zbGUicGfXSNi3o+tfXSNS48U/7K9mIOOCR79Cl3+Q==", - "dev": true, - "requires": { - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-member-expression-to-functions": "^7.12.13", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/helper-replace-supers": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13" - } - }, - "@babel/helper-function-name": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.12.13.tgz", - "integrity": "sha512-TZvmPn0UOqmvi5G4vvw0qZTpVptGkB1GL61R6lKvrSdIxGm5Pky7Q3fpKiIkQCAtRCBUwB0PaThlx9vebCDSwA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.12.13", - "@babel/template": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", - "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.13.tgz", - "integrity": "sha512-B+7nN0gIL8FZ8SvMcF+EPyB21KnCcZHQZFczCxbiNGV/O0rsrSBlWGLzmtBJ3GMjSVMIm4lpFhR+VdVBuIsUcQ==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", - "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", - "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", - "dev": true - }, - "@babel/helper-replace-supers": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.13.tgz", - "integrity": "sha512-pctAOIAMVStI2TMLhozPKbf5yTEXc0OJa0eENheb4w09SrgOWEs+P4nTOZYJQCqs8JlErGLDPDJTiGIp3ygbLg==", - "dev": true, - "requires": { - "@babel/helper-member-expression-to-functions": "^7.12.13", - "@babel/helper-optimise-call-expression": "^7.12.13", - "@babel/traverse": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", - "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", - "dev": true, - "requires": { - "@babel/types": "^7.12.13" - } - }, - "@babel/highlight": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.12.13.tgz", - "integrity": "sha512-kocDQvIbgMKlWxXe9fof3TQ+gkIPOUSEYhJjqUjvKMez3krV7vbzYCDq39Oj11UAVK7JqPVGQPlgE85dPNlQww==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.12.15", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.15.tgz", - "integrity": "sha512-AQBOU2Z9kWwSZMd6lNjCX0GUgFonL1wAM1db8L8PMk9UDaGsRCArBkU4Sc+UCM3AE4hjbXx+h58Lb3QT4oRmrA==", - "dev": true - }, - "@babel/template": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", - "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13" - } - }, - "@babel/traverse": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.13.tgz", - "integrity": "sha512-3Zb4w7eE/OslI0fTp8c7b286/cQps3+vdLW3UcwC8VSJC6GbKn55aeVVu2QJNuCDoeKyptLOFrPq8WqZZBodyA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@babel/generator": "^7.12.13", - "@babel/helper-function-name": "^7.12.13", - "@babel/helper-split-export-declaration": "^7.12.13", - "@babel/parser": "^7.12.13", - "@babel/types": "^7.12.13", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "@babel/types": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", - "integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - } } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", - "integrity": "sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz", + "integrity": "sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", - "integrity": "sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz", + "integrity": "sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA==", "dev": true, "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" } }, "@babel/preset-env": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.11.tgz", - "integrity": "sha512-j8Tb+KKIXKYlDBQyIOy4BLxzv1NUOwlHfZ74rvW+Z0Gp4/cI2IMDPBWAgWceGcE7aep9oL/0K9mlzlMGxA8yNw==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.12.7", - "@babel/helper-compilation-targets": "^7.12.5", - "@babel/helper-module-imports": "^7.12.5", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-validator-option": "^7.12.11", - "@babel/plugin-proposal-async-generator-functions": "^7.12.1", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/plugin-proposal-dynamic-import": "^7.12.1", - "@babel/plugin-proposal-export-namespace-from": "^7.12.1", - "@babel/plugin-proposal-json-strings": "^7.12.1", - "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", - "@babel/plugin-proposal-numeric-separator": "^7.12.7", - "@babel/plugin-proposal-object-rest-spread": "^7.12.1", - "@babel/plugin-proposal-optional-catch-binding": "^7.12.1", - "@babel/plugin-proposal-optional-chaining": "^7.12.7", - "@babel/plugin-proposal-private-methods": "^7.12.1", - "@babel/plugin-proposal-unicode-property-regex": "^7.12.1", + "version": "7.13.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.13.5.tgz", + "integrity": "sha512-xUeKBIIcbwxGevyWMSWZOW98W1lp7toITvVsMxSddCEQy932yYiF4fCB+CG3E/MXzFX3KbefgvCqEQ7TDoE6UQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.5", + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-validator-option": "^7.12.17", + "@babel/plugin-proposal-async-generator-functions": "^7.13.5", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-dynamic-import": "^7.12.17", + "@babel/plugin-proposal-export-namespace-from": "^7.12.13", + "@babel/plugin-proposal-json-strings": "^7.12.13", + "@babel/plugin-proposal-logical-assignment-operators": "^7.12.13", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.13.0", + "@babel/plugin-proposal-numeric-separator": "^7.12.13", + "@babel/plugin-proposal-object-rest-spread": "^7.13.0", + "@babel/plugin-proposal-optional-catch-binding": "^7.12.13", + "@babel/plugin-proposal-optional-chaining": "^7.13.0", + "@babel/plugin-proposal-private-methods": "^7.13.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.12.13", "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.12.1", + "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-dynamic-import": "^7.8.0", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", "@babel/plugin-syntax-json-strings": "^7.8.0", @@ -1145,43 +1004,54 @@ "@babel/plugin-syntax-object-rest-spread": "^7.8.0", "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.12.1", - "@babel/plugin-transform-arrow-functions": "^7.12.1", - "@babel/plugin-transform-async-to-generator": "^7.12.1", - "@babel/plugin-transform-block-scoped-functions": "^7.12.1", - "@babel/plugin-transform-block-scoping": "^7.12.11", - "@babel/plugin-transform-classes": "^7.12.1", - "@babel/plugin-transform-computed-properties": "^7.12.1", - "@babel/plugin-transform-destructuring": "^7.12.1", - "@babel/plugin-transform-dotall-regex": "^7.12.1", - "@babel/plugin-transform-duplicate-keys": "^7.12.1", - "@babel/plugin-transform-exponentiation-operator": "^7.12.1", - "@babel/plugin-transform-for-of": "^7.12.1", - "@babel/plugin-transform-function-name": "^7.12.1", - "@babel/plugin-transform-literals": "^7.12.1", - "@babel/plugin-transform-member-expression-literals": "^7.12.1", - "@babel/plugin-transform-modules-amd": "^7.12.1", - "@babel/plugin-transform-modules-commonjs": "^7.12.1", - "@babel/plugin-transform-modules-systemjs": "^7.12.1", - "@babel/plugin-transform-modules-umd": "^7.12.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.1", - "@babel/plugin-transform-new-target": "^7.12.1", - "@babel/plugin-transform-object-super": "^7.12.1", - "@babel/plugin-transform-parameters": "^7.12.1", - "@babel/plugin-transform-property-literals": "^7.12.1", - "@babel/plugin-transform-regenerator": "^7.12.1", - "@babel/plugin-transform-reserved-words": "^7.12.1", - "@babel/plugin-transform-shorthand-properties": "^7.12.1", - "@babel/plugin-transform-spread": "^7.12.1", - "@babel/plugin-transform-sticky-regex": "^7.12.7", - "@babel/plugin-transform-template-literals": "^7.12.1", - "@babel/plugin-transform-typeof-symbol": "^7.12.10", - "@babel/plugin-transform-unicode-escapes": "^7.12.1", - "@babel/plugin-transform-unicode-regex": "^7.12.1", + "@babel/plugin-syntax-top-level-await": "^7.12.13", + "@babel/plugin-transform-arrow-functions": "^7.13.0", + "@babel/plugin-transform-async-to-generator": "^7.13.0", + "@babel/plugin-transform-block-scoped-functions": "^7.12.13", + "@babel/plugin-transform-block-scoping": "^7.12.13", + "@babel/plugin-transform-classes": "^7.13.0", + "@babel/plugin-transform-computed-properties": "^7.13.0", + "@babel/plugin-transform-destructuring": "^7.13.0", + "@babel/plugin-transform-dotall-regex": "^7.12.13", + "@babel/plugin-transform-duplicate-keys": "^7.12.13", + "@babel/plugin-transform-exponentiation-operator": "^7.12.13", + "@babel/plugin-transform-for-of": "^7.13.0", + "@babel/plugin-transform-function-name": "^7.12.13", + "@babel/plugin-transform-literals": "^7.12.13", + "@babel/plugin-transform-member-expression-literals": "^7.12.13", + "@babel/plugin-transform-modules-amd": "^7.13.0", + "@babel/plugin-transform-modules-commonjs": "^7.13.0", + "@babel/plugin-transform-modules-systemjs": "^7.12.13", + "@babel/plugin-transform-modules-umd": "^7.13.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.13", + "@babel/plugin-transform-new-target": "^7.12.13", + "@babel/plugin-transform-object-super": "^7.12.13", + "@babel/plugin-transform-parameters": "^7.13.0", + "@babel/plugin-transform-property-literals": "^7.12.13", + "@babel/plugin-transform-regenerator": "^7.12.13", + "@babel/plugin-transform-reserved-words": "^7.12.13", + "@babel/plugin-transform-shorthand-properties": "^7.12.13", + "@babel/plugin-transform-spread": "^7.13.0", + "@babel/plugin-transform-sticky-regex": "^7.12.13", + "@babel/plugin-transform-template-literals": "^7.13.0", + "@babel/plugin-transform-typeof-symbol": "^7.12.13", + "@babel/plugin-transform-unicode-escapes": "^7.12.13", + "@babel/plugin-transform-unicode-regex": "^7.12.13", "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.12.11", - "core-js-compat": "^3.8.0", - "semver": "^5.5.0" + "@babel/types": "^7.13.0", + "babel-plugin-polyfill-corejs2": "^0.1.4", + "babel-plugin-polyfill-corejs3": "^0.1.3", + "babel-plugin-polyfill-regenerator": "^0.1.2", + "core-js-compat": "^3.9.0", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", + "dev": true + } } }, "@babel/preset-modules": { @@ -1198,73 +1068,65 @@ } }, "@babel/preset-typescript": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.12.13.tgz", - "integrity": "sha512-gYry7CeXwD2wtw5qHzrtzKaShEhOfTmKb4i0ZxeYBcBosN5VuAudsNbjX7Oj5EAfQ3K4s4HsVMQRRcqGsPvs2A==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.13.0.tgz", + "integrity": "sha512-LXJwxrHy0N3f6gIJlYbLta1D9BDtHpQeqwzM0LIfjDlr6UE/D5Mc7W4iDiQzaE+ks0sTjT26ArcHWnJVt0QiHw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.12.13", - "@babel/helper-validator-option": "^7.12.11", - "@babel/plugin-transform-typescript": "^7.12.13" - }, - "dependencies": { - "@babel/helper-plugin-utils": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.12.13.tgz", - "integrity": "sha512-C+10MXCXJLiR6IeG9+Wiejt9jmtFpxUc3MQqCmPY8hfCjyUGl9kT+B2okzEZrtykiwrc4dbCPdDoz0A/HQbDaA==", - "dev": true - } + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-validator-option": "^7.12.17", + "@babel/plugin-transform-typescript": "^7.13.0" } }, "@babel/runtime": { - "version": "7.12.18", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.18.tgz", - "integrity": "sha512-BogPQ7ciE6SYAUPtlm9tWbgI9+2AgqSam6QivMgXgAT+fKbgppaj4ZX15MHeLC1PVF5sNk70huBu20XxWOs8Cg==", + "version": "7.13.6", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.13.6.tgz", + "integrity": "sha512-Y/DEVhSQ91u27rxq7D0EH/sewS6+x06p/MgO1VppbDHMzYXLZrAR5cFjCom78e9RUw1BQAq6qJg6fXc/ep7glA==", "requires": { "regenerator-runtime": "^0.13.4" } }, "@babel/runtime-corejs3": { - "version": "7.12.18", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.12.18.tgz", - "integrity": "sha512-ngR7yhNTjDxxe1VYmhqQqqXZWujGb6g0IoA4qeG6MxNGRnIw2Zo8ImY8HfaQ7l3T6GklWhdNfyhWk0C0iocdVA==", + "version": "7.13.6", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.13.6.tgz", + "integrity": "sha512-wSbWQEOD7tk8zjRJVy41L85v+ctDk6AIv72hzVOyOhAeRSkzxKhamBP2jLPBYLYvLNgiJqa4gnpiYAIeedf/sA==", "requires": { "core-js-pure": "^3.0.0", "regenerator-runtime": "^0.13.4" } }, "@babel/template": { - "version": "7.12.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.7.tgz", - "integrity": "sha512-GkDzmHS6GV7ZeXfJZ0tLRBhZcMcY0/Lnb+eEbXDBfCAcZCjrZKe6p3J4we/D24O9Y8enxWAg1cWwof59yLh2ow==", + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", "dev": true, "requires": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.12.7", - "@babel/types": "^7.12.7" + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" } }, "@babel/traverse": { - "version": "7.12.12", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.12.tgz", - "integrity": "sha512-s88i0X0lPy45RrLM8b9mz8RPH5FqO9G9p7ti59cToE44xFm1Q+Pjh5Gq4SXBbtb88X7Uy7pexeqRIQDDMNkL0w==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz", + "integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==", "dev": true, "requires": { - "@babel/code-frame": "^7.12.11", - "@babel/generator": "^7.12.11", - "@babel/helper-function-name": "^7.12.11", - "@babel/helper-split-export-declaration": "^7.12.11", - "@babel/parser": "^7.12.11", - "@babel/types": "^7.12.12", + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.13.0", + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.13.0", + "@babel/types": "^7.13.0", "debug": "^4.1.0", "globals": "^11.1.0", "lodash": "^4.17.19" } }, "@babel/types": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.13.tgz", - "integrity": "sha512-oKrdZTld2im1z8bDwTOQvUbxKwE+854zc16qWZQlcTqMN00pWxHQ4ZeOq0yDMnisOpRykH2/5Qqcrk/OlbAjiQ==", + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz", + "integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.12.11", @@ -1346,9 +1208,9 @@ } }, "@ethersproject/abstract-provider": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.0.8.tgz", - "integrity": "sha512-fqJXkewcGdi8LogKMgRyzc/Ls2js07yor7+g9KfPs09uPOcQLg7cc34JN+lk34HH9gg2HU0DIA5797ZR8znkfw==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-provider/-/abstract-provider-5.0.9.tgz", + "integrity": "sha512-X9fMkqpeu9ayC3JyBkeeZhn35P4xQkpGX/l+FrxDtEW9tybf/UWXSMi8bGThpPtfJ6q6U2LDetXSpSwK4TfYQQ==", "requires": { "@ethersproject/bignumber": "^5.0.13", "@ethersproject/bytes": "^5.0.9", @@ -1360,9 +1222,9 @@ } }, "@ethersproject/abstract-signer": { - "version": "5.0.11", - "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.0.11.tgz", - "integrity": "sha512-RKOgPSEYafknA62SrD3OCK42AllHE4YBfKYXyQeM+sBP7Nq3X5FpzeoY4uzC43P4wIhmNoTHCKQuwnX7fBqb6Q==", + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@ethersproject/abstract-signer/-/abstract-signer-5.0.13.tgz", + "integrity": "sha512-VBIZEI5OK0TURoCYyw0t3w+TEO4kdwnI9wvt4kqUwyxSn3YCRpXYVl0Xoe7XBR/e5+nYOi2MyFGJ3tsFwONecQ==", "requires": { "@ethersproject/abstract-provider": "^5.0.8", "@ethersproject/bignumber": "^5.0.13", @@ -1372,9 +1234,9 @@ } }, "@ethersproject/address": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.0.9.tgz", - "integrity": "sha512-gKkmbZDMyGbVjr8nA5P0md1GgESqSGH7ILIrDidPdNXBl4adqbuA3OAuZx/O2oGpL6PtJ9BDa0kHheZ1ToHU3w==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/address/-/address-5.0.10.tgz", + "integrity": "sha512-70vqESmW5Srua1kMDIN6uVfdneZMaMyRYH4qPvkAXGkbicrCOsA9m01vIloA4wYiiF+HLEfL1ENKdn5jb9xiAw==", "requires": { "@ethersproject/bignumber": "^5.0.13", "@ethersproject/bytes": "^5.0.9", @@ -1384,26 +1246,26 @@ } }, "@ethersproject/base64": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.0.7.tgz", - "integrity": "sha512-S5oh5DVfCo06xwJXT8fQC68mvJfgScTl2AXvbYMsHNfIBTDb084Wx4iA9MNlEReOv6HulkS+gyrUM/j3514rSw==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/base64/-/base64-5.0.8.tgz", + "integrity": "sha512-PNbpHOMgZpZ1skvQl119pV2YkCPXmZTxw+T92qX0z7zaMFPypXWTZBzim+hUceb//zx4DFjeGT4aSjZRTOYThg==", "requires": { "@ethersproject/bytes": "^5.0.9" } }, "@ethersproject/basex": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.0.7.tgz", - "integrity": "sha512-OsXnRsujGmYD9LYyJlX+cVe5KfwgLUbUJrJMWdzRWogrygXd5HvGd7ygX1AYjlu1z8W/+t2FoQnczDR/H2iBjA==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/basex/-/basex-5.0.8.tgz", + "integrity": "sha512-PCVKZIShBQUqAXjJSvaCidThPvL0jaaQZcewJc0sf8Xx05BizaOS8r3jdPdpNdY+/qZtRDqwHTSKjvR/xssyLQ==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/properties": "^5.0.7" } }, "@ethersproject/bignumber": { - "version": "5.0.13", - "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.13.tgz", - "integrity": "sha512-b89bX5li6aK492yuPP5mPgRVgIxxBP7ksaBtKX5QQBsrZTpNOjf/MR4CjcUrAw8g+RQuD6kap9lPjFgY4U1/5A==", + "version": "5.0.14", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.0.14.tgz", + "integrity": "sha512-Q4TjMq9Gg3Xzj0aeJWqJgI3tdEiPiET7Y5OtNtjTAODZ2kp4y9jMNg97zVcvPedFvGROdpGDyCI77JDFodUzOw==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8", @@ -1411,25 +1273,25 @@ } }, "@ethersproject/bytes": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.9.tgz", - "integrity": "sha512-k+17ZViDtAugC0s7HM6rdsTWEdIYII4RPCDkPEuxKc6i40Bs+m6tjRAtCECX06wKZnrEoR9pjOJRXHJ/VLoOcA==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.0.10.tgz", + "integrity": "sha512-vpu0v1LZ1j1s9kERQIMnVU69MyHEzUff7nqK9XuCU4vx+AM8n9lU2gj7jtJIvGSt9HzatK/6I6bWusI5nyuaTA==", "requires": { "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/constants": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.8.tgz", - "integrity": "sha512-sCc73pFBsl59eDfoQR5OCEZCRv5b0iywadunti6MQIr5lt3XpwxK1Iuzd8XSFO02N9jUifvuZRrt0cY0+NBgTg==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.0.9.tgz", + "integrity": "sha512-2uAKH89UcaJP/Sc+54u92BtJtZ4cPgcS1p0YbB1L3tlkavwNvth+kNCUplIB1Becqs7BOZr0B/3dMNjhJDy4Dg==", "requires": { "@ethersproject/bignumber": "^5.0.13" } }, "@ethersproject/contracts": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.0.9.tgz", - "integrity": "sha512-CCTxVeDh6sjdSEbjzONhtwPjECvaHE62oGkY8M7kP0CHmgLD2SEGel0HZib8e5oQKRKGly9AKcUFW4g3rQ0AQw==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@ethersproject/contracts/-/contracts-5.0.11.tgz", + "integrity": "sha512-FTUUd/6x00dYL2VufE2VowZ7h3mAyBfCQMGwI3tKDIWka+C0CunllFiKrlYCdiHFuVeMotR65dIcnzbLn72MCw==", "requires": { "@ethersproject/abi": "^5.0.10", "@ethersproject/abstract-provider": "^5.0.8", @@ -1443,9 +1305,9 @@ } }, "@ethersproject/hash": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.0.10.tgz", - "integrity": "sha512-Tf0bvs6YFhw28LuHnhlDWyr0xfcDxSXdwM4TcskeBbmXVSKLv3bJQEEEBFUcRX0fJuslR3gCVySEaSh7vuMx5w==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@ethersproject/hash/-/hash-5.0.11.tgz", + "integrity": "sha512-H3KJ9fk33XWJ2djAW03IL7fg3DsDMYjO1XijiUb1hJ85vYfhvxu0OmsU7d3tg2Uv1H1kFSo8ghr3WFQ8c+NL3g==", "requires": { "@ethersproject/abstract-signer": "^5.0.10", "@ethersproject/address": "^5.0.9", @@ -1458,9 +1320,9 @@ } }, "@ethersproject/hdnode": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.0.8.tgz", - "integrity": "sha512-Mscpjd7BBjxYSWghaNMwV0xrBBkOoCq6YEPRm9MgE24CiBlzzbfEB5DGq6hiZqhQaxPkdCUtKKqZi3nt9hx43g==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/hdnode/-/hdnode-5.0.9.tgz", + "integrity": "sha512-S5UMmIC6XfFtqhUK4uTjD8GPNzSbE+sZ/0VMqFnA3zAJ+cEFZuEyhZDYnl2ItGJzjT4jsy+uEy1SIl3baYK1PQ==", "requires": { "@ethersproject/abstract-signer": "^5.0.10", "@ethersproject/basex": "^5.0.7", @@ -1477,9 +1339,9 @@ } }, "@ethersproject/json-wallets": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.0.10.tgz", - "integrity": "sha512-Ux36u+d7Dm0M5AQ+mWuHdvfGPMN8K1aaLQgwzrsD4ELTWlwRuHuQbmn7/GqeOpbfaV6POLwdYcBk2TXjlGp/IQ==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@ethersproject/json-wallets/-/json-wallets-5.0.11.tgz", + "integrity": "sha512-0GhWScWUlXXb4qJNp0wmkU95QS3YdN9UMOfMSEl76CRANWWrmyzxcBVSXSBu5iQ0/W8wO+xGlJJ3tpA6v3mbIw==", "requires": { "@ethersproject/abstract-signer": "^5.0.10", "@ethersproject/address": "^5.0.9", @@ -1497,48 +1359,48 @@ } }, "@ethersproject/keccak256": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.0.7.tgz", - "integrity": "sha512-zpUBmofWvx9PGfc7IICobgFQSgNmTOGTGLUxSYqZzY/T+b4y/2o5eqf/GGmD7qnTGzKQ42YlLNo+LeDP2qe55g==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.0.8.tgz", + "integrity": "sha512-zoGbwXcWWs9MX4NOAZ7N0hhgIRl4Q/IO/u9c/RHRY4WqDy3Ywm0OLamEV53QDwhjwn3YiiVwU1Ve5j7yJ0a/KQ==", "requires": { "@ethersproject/bytes": "^5.0.9", "js-sha3": "0.5.7" } }, "@ethersproject/logger": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.8.tgz", - "integrity": "sha512-SkJCTaVTnaZ3/ieLF5pVftxGEFX56pTH+f2Slrpv7cU0TNpUZNib84QQdukd++sWUp/S7j5t5NW+WegbXd4U/A==" + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.0.9.tgz", + "integrity": "sha512-kV3Uamv3XOH99Xf3kpIG3ZkS7mBNYcLDM00JSDtNgNB4BihuyxpQzIZPRIDmRi+95Z/R1Bb0X2kUNHa/kJoVrw==" }, "@ethersproject/networks": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.0.7.tgz", - "integrity": "sha512-dI14QATndIcUgcCBL1c5vUr/YsI5cCHLN81rF7PU+yS7Xgp2/Rzbr9+YqpC6NBXHFUASjh6GpKqsVMpufAL0BQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/networks/-/networks-5.0.8.tgz", + "integrity": "sha512-PYpptlO2Tu5f/JEBI5hdlMds5k1DY1QwVbh3LKPb3un9dQA2bC51vd2/gRWAgSBpF3kkmZOj4FhD7ATLX4H+DA==", "requires": { "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/pbkdf2": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.0.7.tgz", - "integrity": "sha512-0SNLNixPMqnosH6pyc4yPiUu/C9/Jbu+f6I8GJW9U2qNpMBddmRJviwseoha5Zw1V+Aw0Z/yvYyzIIE8yPXqLA==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/pbkdf2/-/pbkdf2-5.0.8.tgz", + "integrity": "sha512-UlmAMGbIPaS2xXsI38FbePVTfJMuU9jnwcqVn3p88HxPF4kD897ha+l3TNsBqJqf32UbQL5GImnf1oJkSKq4vQ==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/sha2": "^5.0.7" } }, "@ethersproject/properties": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.7.tgz", - "integrity": "sha512-812H1Rus2vjw0zbasfDI1GLNPDsoyX1pYqiCgaR1BuyKxUTbwcH1B+214l6VGe1v+F6iEVb7WjIwMjKhb4EUsg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/properties/-/properties-5.0.8.tgz", + "integrity": "sha512-zEnLMze2Eu2VDPj/05QwCwMKHh506gpT9PP9KPVd4dDB+5d6AcROUYVLoIIQgBYK7X/Gw0UJmG3oVtnxOQafAw==", "requires": { "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/providers": { - "version": "5.0.19", - "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.0.19.tgz", - "integrity": "sha512-G+flo1jK1y/rvQy6b71+Nu7qOlkOKz+XqpgqFMZslkCzGuzQRmk9Qp7Ln4soK8RSyP1e5TCujaRf1H+EZahoaw==", + "version": "5.0.23", + "resolved": "https://registry.npmjs.org/@ethersproject/providers/-/providers-5.0.23.tgz", + "integrity": "sha512-eJ94z2tgPaUgUmxwd3BVkIzkgkbNIkY6wRPVas04LVaBTycObQbgj794aaUu2bfk7+Bn2B/gjUZtJW1ybxh9/A==", "requires": { "@ethersproject/abstract-provider": "^5.0.8", "@ethersproject/abstract-signer": "^5.0.10", @@ -1569,27 +1431,27 @@ } }, "@ethersproject/random": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.0.7.tgz", - "integrity": "sha512-PxSRWwN3s+FH9AWMZU6AcWJsNQ9KzqKV6NgdeKPtxahdDjCuXxTAuzTZNXNRK+qj+Il351UnweAGd+VuZcOAlQ==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/random/-/random-5.0.8.tgz", + "integrity": "sha512-4rHtotmd9NjklW0eDvByicEkL+qareIyFSbG1ShC8tPJJSAC0g55oQWzw+3nfdRCgBHRuEE7S8EcPcTVPvZ9cA==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/rlp": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.0.7.tgz", - "integrity": "sha512-ulUTVEuV7PT4jJTPpfhRHK57tkLEDEY9XSYJtrSNHOqdwMvH0z7BM2AKIMq4LVDlnu4YZASdKrkFGEIO712V9w==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/rlp/-/rlp-5.0.8.tgz", + "integrity": "sha512-E4wdFs8xRNJfzNHmnkC8w5fPeT4Wd1U2cust3YeT16/46iSkLT8nn8ilidC6KhR7hfuSZE4UqSPzyk76p7cdZg==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8" } }, "@ethersproject/sha2": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.0.7.tgz", - "integrity": "sha512-MbUqz68hhp5RsaZdqi1eg1rrtiqt5wmhRYqdA7MX8swBkzW2KiLgK+Oh25UcWhUhdi1ImU9qrV6if5j0cC7Bxg==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.0.8.tgz", + "integrity": "sha512-ILP1ZgyvDj4rrdE+AXrTv9V88m7x87uga2VZ/FeULKPumOEw/4bGnJz/oQ8zDnDvVYRCJ+48VaQBS2CFLbk1ww==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8", @@ -1608,20 +1470,20 @@ } }, "@ethersproject/signing-key": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.0.8.tgz", - "integrity": "sha512-YKxQM45eDa6WAD+s3QZPdm1uW1MutzVuyoepdRRVmMJ8qkk7iOiIhUkZwqKLNxKzEJijt/82ycuOREc9WBNAKg==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/signing-key/-/signing-key-5.0.10.tgz", + "integrity": "sha512-w5it3GbFOvN6e0mTd5gDNj+bwSe6L9jqqYjU+uaYS8/hAEp4qYLk5p8ZjbJJkNn7u1p0iwocp8X9oH/OdK8apA==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/logger": "^5.0.8", "@ethersproject/properties": "^5.0.7", - "elliptic": "6.5.3" + "elliptic": "6.5.4" } }, "@ethersproject/solidity": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.0.8.tgz", - "integrity": "sha512-OJkyBq9KaoGsi8E8mYn6LX+vKyCURvxSp0yuGBcOqEFM3vkn9PsCiXsHdOXdNBvlHG5evJXwAYC2UR0TzgJeKA==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/solidity/-/solidity-5.0.9.tgz", + "integrity": "sha512-LIxSAYEQgLRXE3mRPCq39ou61kqP8fDrGqEeNcaNJS3aLbmAOS8MZp56uK++WsdI9hj8sNsFh78hrAa6zR9Jag==", "requires": { "@ethersproject/bignumber": "^5.0.13", "@ethersproject/bytes": "^5.0.9", @@ -1641,9 +1503,9 @@ } }, "@ethersproject/transactions": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.0.9.tgz", - "integrity": "sha512-0Fu1yhdFBkrbMjenEr+39tmDxuHmaw0pe9Jb18XuKoItj7Z3p7+UzdHLr2S/okvHDHYPbZE5gtANDdQ3ZL1nBA==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/transactions/-/transactions-5.0.10.tgz", + "integrity": "sha512-Tqpp+vKYQyQdJQQk4M73tDzO7ODf2D42/sJOcKlDAAbdSni13v6a+31hUdo02qYXhVYwIs+ZjHnO4zKv5BNk8w==", "requires": { "@ethersproject/address": "^5.0.9", "@ethersproject/bignumber": "^5.0.13", @@ -1657,9 +1519,9 @@ } }, "@ethersproject/units": { - "version": "5.0.9", - "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.0.9.tgz", - "integrity": "sha512-4jIkcMVrJ3lCgXMO4M/2ww0/T/IN08vJTZld7FIAwa6aoBDTAy71+sby3sShl1SG3HEeKYbI3fBWauCUgPRUpQ==", + "version": "5.0.10", + "resolved": "https://registry.npmjs.org/@ethersproject/units/-/units-5.0.10.tgz", + "integrity": "sha512-eaiHi9ham5lbC7qpqxpae7OY/nHJUnRUnFFuEwi2VB5Nwe3Np468OAV+e+HR+jAK4fHXQE6PFBTxWGtnZuO37g==", "requires": { "@ethersproject/bignumber": "^5.0.13", "@ethersproject/constants": "^5.0.8", @@ -1667,9 +1529,9 @@ } }, "@ethersproject/wallet": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.0.10.tgz", - "integrity": "sha512-5siYr38NhqZKH6DUr6u4PdhgOKur8Q6sw+JID2TitEUmW0tOl8f6rpxAe77tw6SJT60D2UcvgsyLtl32+Nl+ig==", + "version": "5.0.11", + "resolved": "https://registry.npmjs.org/@ethersproject/wallet/-/wallet-5.0.11.tgz", + "integrity": "sha512-2Fg/DOvUltR7aZTOyWWlQhru+SKvq2UE3uEhXSyCFgMqDQNuc2nHXh1SHJtN65jsEbjVIppOe1Q7EQMvhmeeRw==", "requires": { "@ethersproject/abstract-provider": "^5.0.8", "@ethersproject/abstract-signer": "^5.0.10", @@ -1689,9 +1551,9 @@ } }, "@ethersproject/web": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.0.12.tgz", - "integrity": "sha512-gVxS5iW0bgidZ76kr7LsTxj4uzN5XpCLzvZrLp8TP+4YgxHfCeetFyQkRPgBEAJdNrexdSBayvyJvzGvOq0O8g==", + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/@ethersproject/web/-/web-5.0.13.tgz", + "integrity": "sha512-G3x/Ns7pQm21ALnWLbdBI5XkW/jrsbXXffI9hKNPHqf59mTxHYtlNiSwxdoTSwCef3Hn7uvGZpaSgTyxs7IufQ==", "requires": { "@ethersproject/base64": "^5.0.7", "@ethersproject/bytes": "^5.0.9", @@ -1701,9 +1563,9 @@ } }, "@ethersproject/wordlists": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.0.8.tgz", - "integrity": "sha512-px2mloc1wAcdTbzv0ZotTx+Uh/dfnDO22D9Rx8xr7+/PUwAhZQjoJ9t7Hn72nsaN83rWBXsLvFcIRZju4GIaEQ==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/@ethersproject/wordlists/-/wordlists-5.0.9.tgz", + "integrity": "sha512-Sn6MTjZkfbriod6GG6+p43W09HOXT4gwcDVNj0YoPYlo4Zq2Fk6b1CU9KUX3c6aI17PrgYb4qwZm5BMuORyqyQ==", "requires": { "@ethersproject/bytes": "^5.0.9", "@ethersproject/hash": "^5.0.10", @@ -1734,9 +1596,9 @@ } }, "@istanbuljs/schema": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz", - "integrity": "sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", + "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", "dev": true }, "@jest/console": { @@ -1846,12 +1708,6 @@ "strip-ansi": "^6.0.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2378,24 +2234,13 @@ } }, "@npmcli/move-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.0.tgz", - "integrity": "sha512-Iv2iq0JuyYjKeFkSR4LPaCdDZwlGK9X2cP/01nJcp3yMJ1FjNd9vpiEYvLUgzBxKPg2SFmaOhizoQsPc0LWeOQ==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", "dev": true, "requires": { "mkdirp": "^1.0.4", - "rimraf": "^2.7.1" - }, - "dependencies": { - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } + "rimraf": "^3.0.2" } }, "@peculiar/asn1-schema": { @@ -2517,9 +2362,9 @@ "dev": true }, "@types/graceful-fs": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.4.tgz", - "integrity": "sha512-mWA/4zFQhfvOA8zWkXobwJvBD7vzcxgrOQ0J5CH1votGqdq9m7+FwtGaqyCZqC3NyyBkc9z4m+iry4LlqcMWJg==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", + "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", "dev": true, "requires": { "@types/node": "*" @@ -2560,9 +2405,9 @@ } }, "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", "dev": true }, "@types/json5": { @@ -2571,10 +2416,25 @@ "integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=", "dev": true }, + "@types/lodash": { + "version": "4.14.168", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.168.tgz", + "integrity": "sha512-oVfRvqHV/V6D1yifJbVRU3TMp8OT6o6BG+U9MkwuJ3U8/CsDHvalRpsxBqivn71ztOFZBTfJMvETbqHiaNSj7Q==", + "dev": true + }, + "@types/lodash.uniqueid": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@types/lodash.uniqueid/-/lodash.uniqueid-4.0.6.tgz", + "integrity": "sha512-WXXsDm7Q1SiAeCG9ubCiDxOuLEDi5x+Crx8SgwTFgBtofATwK1jAeSbGz2bHlfqWezi7mcjynenlBFCeDLhHlw==", + "dev": true, + "requires": { + "@types/lodash": "*" + } + }, "@types/node": { - "version": "14.14.21", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.21.tgz", - "integrity": "sha512-cHYfKsnwllYhjOzuC5q1VpguABBeecUp24yFluHpn/BQaVxB1CuQ1FSRZCzrPxrkIfWISXV2LbeoBthLWg0+0A==", + "version": "14.14.31", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", + "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==", "dev": true }, "@types/normalize-package-data": { @@ -2584,9 +2444,9 @@ "dev": true }, "@types/prettier": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.1.6.tgz", - "integrity": "sha512-6gOkRe7OIioWAXfnO/2lFiv+SJichKVSys1mSsgyrYHSEjk8Ctv4tSR/Odvnu+HWlH2C8j53dahU03XmQdd5fA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.1.tgz", + "integrity": "sha512-DxZZbyMAM9GWEzXL+BMZROWz9oo6A9EilwwOMET2UVu2uZTqMWS5S69KVtuVKaRjCUpcrOXRalet86/OpG4kqw==", "dev": true }, "@types/qs": { @@ -2601,10 +2461,16 @@ "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", "dev": true }, + "@types/uuid": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.0.tgz", + "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", + "dev": true + }, "@types/yargs": { - "version": "15.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.12.tgz", - "integrity": "sha512-f+fD/fQAo3BCbCDlrUpznF1A5Zp9rB0noS5vnoormHSIPFKL0Z2DcUJ3Gxp5ytH4uLRNxy7AwYUC9exZzqGMAw==", + "version": "15.0.13", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", + "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -2627,13 +2493,13 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.15.1.tgz", - "integrity": "sha512-yW2epMYZSpNJXZy22Biu+fLdTG8Mn6b22kR3TqblVk50HGNV8Zya15WAXuQCr8tKw4Qf1BL4QtI6kv6PCkLoJw==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.15.2.tgz", + "integrity": "sha512-uiQQeu9tWl3f1+oK0yoAv9lt/KXO24iafxgQTkIYO/kitruILGx3uH+QtIAHqxFV+yIsdnJH+alel9KuE3J15Q==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.15.1", - "@typescript-eslint/scope-manager": "4.15.1", + "@typescript-eslint/experimental-utils": "4.15.2", + "@typescript-eslint/scope-manager": "4.15.2", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", "lodash": "^4.17.15", @@ -2654,55 +2520,55 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.1.tgz", - "integrity": "sha512-9LQRmOzBRI1iOdJorr4jEnQhadxK4c9R2aEAsm7WE/7dq8wkKD1suaV0S/JucTL8QlYUPU1y2yjqg+aGC0IQBQ==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.2.tgz", + "integrity": "sha512-Fxoshw8+R5X3/Vmqwsjc8nRO/7iTysRtDqx6rlfLZ7HbT8TZhPeQqbPjTyk2RheH3L8afumecTQnUc9EeXxohQ==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.15.1", - "@typescript-eslint/types": "4.15.1", - "@typescript-eslint/typescript-estree": "4.15.1", + "@typescript-eslint/scope-manager": "4.15.2", + "@typescript-eslint/types": "4.15.2", + "@typescript-eslint/typescript-estree": "4.15.2", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" } }, "@typescript-eslint/parser": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.15.1.tgz", - "integrity": "sha512-V8eXYxNJ9QmXi5ETDguB7O9diAXlIyS+e3xzLoP/oVE4WCAjssxLIa0mqCLsCGXulYJUfT+GV70Jv1vHsdKwtA==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.15.2.tgz", + "integrity": "sha512-SHeF8xbsC6z2FKXsaTb1tBCf0QZsjJ94H6Bo51Y1aVEZ4XAefaw5ZAilMoDPlGghe+qtq7XdTiDlGfVTOmvA+Q==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.15.1", - "@typescript-eslint/types": "4.15.1", - "@typescript-eslint/typescript-estree": "4.15.1", + "@typescript-eslint/scope-manager": "4.15.2", + "@typescript-eslint/types": "4.15.2", + "@typescript-eslint/typescript-estree": "4.15.2", "debug": "^4.1.1" } }, "@typescript-eslint/scope-manager": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.1.tgz", - "integrity": "sha512-ibQrTFcAm7yG4C1iwpIYK7vDnFg+fKaZVfvyOm3sNsGAerKfwPVFtYft5EbjzByDJ4dj1WD8/34REJfw/9wdVA==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.2.tgz", + "integrity": "sha512-Zm0tf/MSKuX6aeJmuXexgdVyxT9/oJJhaCkijv0DvJVT3ui4zY6XYd6iwIo/8GEZGy43cd7w1rFMiCLHbRzAPQ==", "dev": true, "requires": { - "@typescript-eslint/types": "4.15.1", - "@typescript-eslint/visitor-keys": "4.15.1" + "@typescript-eslint/types": "4.15.2", + "@typescript-eslint/visitor-keys": "4.15.2" } }, "@typescript-eslint/types": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.1.tgz", - "integrity": "sha512-iGsaUyWFyLz0mHfXhX4zO6P7O3sExQpBJ2dgXB0G5g/8PRVfBBsmQIc3r83ranEQTALLR3Vko/fnCIVqmH+mPw==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.2.tgz", + "integrity": "sha512-r7lW7HFkAarfUylJ2tKndyO9njwSyoy6cpfDKWPX6/ctZA+QyaYscAHXVAfJqtnY6aaTwDYrOhp+ginlbc7HfQ==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.1.tgz", - "integrity": "sha512-z8MN3CicTEumrWAEB2e2CcoZa3KP9+SMYLIA2aM49XW3cWIaiVSOAGq30ffR5XHxRirqE90fgLw3e6WmNx5uNw==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.2.tgz", + "integrity": "sha512-cGR8C2g5SPtHTQvAymEODeqx90pJHadWsgTtx6GbnTWKqsg7yp6Eaya9nFzUd4KrKhxdYTTFBiYeTPQaz/l8bw==", "dev": true, "requires": { - "@typescript-eslint/types": "4.15.1", - "@typescript-eslint/visitor-keys": "4.15.1", + "@typescript-eslint/types": "4.15.2", + "@typescript-eslint/visitor-keys": "4.15.2", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -2722,12 +2588,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.1.tgz", - "integrity": "sha512-tYzaTP9plooRJY8eNlpAewTOqtWW/4ff/5wBjNVaJ0S0wC4Gpq/zDVRTJa5bq2v1pCNQ08xxMCndcvR+h7lMww==", + "version": "4.15.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.2.tgz", + "integrity": "sha512-TME1VgSb7wTwgENN5KVj4Nqg25hP8DisXxNBojM4Nn31rYaNDIocNm5cmjOFfh42n7NVERxWrDFoETO/76ePyg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.15.1", + "@typescript-eslint/types": "4.15.2", "eslint-visitor-keys": "^2.0.0" } }, @@ -2907,24 +2773,24 @@ } }, "@webpack-cli/configtest": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.0.tgz", - "integrity": "sha512-Un0SdBoN1h4ACnIO7EiCjWuyhNI0Jl96JC+63q6xi4HDUYRZn8Auluea9D+v9NWKc5J4sICVEltdBaVjLX39xw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.1.tgz", + "integrity": "sha512-B+4uBUYhpzDXmwuo3V9yBH6cISwxEI4J+NO5ggDaGEEHb0osY/R7MzeKc0bHURXQuZjMM4qD+bSJCKIuI3eNBQ==", "dev": true }, "@webpack-cli/info": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.1.tgz", - "integrity": "sha512-fLnDML5HZ5AEKzHul8xLAksoKN2cibu6MgonkUj8R9V7bbeVRkd1XbGEGWrAUNYHbX1jcqCsDEpBviE5StPMzQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.2.tgz", + "integrity": "sha512-5U9kUJHnwU+FhKH4PWGZuBC1hTEPYyxGSL5jjoBI96Gx8qcYJGOikpiIpFoTq8mmgX3im2zAo2wanv/alD74KQ==", "dev": true, "requires": { "envinfo": "^7.7.3" } }, "@webpack-cli/serve": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.2.2.tgz", - "integrity": "sha512-03GkWxcgFfm8+WIwcsqJb9agrSDNDDoxaNnexPnCCexP5SCE4IgFd9lNpSy+K2nFqVMpgTFw6SwbmVAVTndVew==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.3.0.tgz", + "integrity": "sha512-k2p2VrONcYVX1wRRrf0f3X2VGltLWcv+JzXRBDmvCxGlCeESx4OXw91TsWeKOkp784uNoVQo313vxJFHXPPwfw==", "dev": true }, "@xtuc/ieee754": { @@ -3061,9 +2927,9 @@ } }, "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", "dev": true }, "ansi-styles": { @@ -3136,15 +3002,15 @@ "dev": true }, "array-includes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.2.tgz", - "integrity": "sha512-w2GspexNQpx+PutG3QpT437/BenZBj0M/MZGn5mzv/MofYqo0xmRHzn4lFsoDlWJ+THYsGJmFlW68WlDFx7VRw==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.3.tgz", + "integrity": "sha512-gcem1KlBU7c9rB+Rq8/3PPKsK2kjqeEBa3bD5kkQo4nYlOHQCJqIJFqBXDEfwaRuYTT4E+FxA9xez7Gf/e3Q7A==", "dev": true, "requires": { - "call-bind": "^1.0.0", + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1", - "get-intrinsic": "^1.0.1", + "es-abstract": "^1.18.0-next.2", + "get-intrinsic": "^1.1.1", "is-string": "^1.0.5" } }, @@ -3198,13 +3064,6 @@ "integrity": "sha512-yG89F0j9B4B0MKIcFyWWxnpZPLaNTjCj4tkE3fjbAoo0qmpGw0PYYqSbX/4ebnd9Icn8ZgK4K1fvDyEtW1JYtQ==", "requires": { "pvutils": "^1.0.17" - }, - "dependencies": { - "pvutils": { - "version": "1.0.17", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.0.17.tgz", - "integrity": "sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ==" - } } }, "assert": { @@ -3534,6 +3393,44 @@ "@types/babel__traverse": "^7.0.6" } }, + "babel-plugin-polyfill-corejs2": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.1.5.tgz", + "integrity": "sha512-5IzdFIjYWqlOFVr/hMYUpc+5fbfuvJTAISwIY58jhH++ZtawtNlcJnxAixlk8ahVwHCz1ipW/kpXYliEBp66wg==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.0", + "@babel/helper-define-polyfill-provider": "^0.1.2", + "semver": "^6.1.1" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + } + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.1.4.tgz", + "integrity": "sha512-ysSzFn/qM8bvcDAn4mC7pKk85Y5dVaoa9h4u0mHxOEpDzabsseONhUpR7kHxpUinfj1bjU7mUZqD23rMZBoeSg==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.1.2", + "core-js-compat": "^3.8.1" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.1.3.tgz", + "integrity": "sha512-hRjTJQiOYt/wBKEc+8V8p9OJ9799blAJcuKzn1JXh3pApHoWl1Emxh2BHc6MC7Qt6bbr3uDpNxaYQnATLIudEg==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.1.2" + } + }, "babel-plugin-syntax-class-properties": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz", @@ -3996,16 +3893,16 @@ } }, "browserslist": { - "version": "4.16.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.1.tgz", - "integrity": "sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA==", + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", + "integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001173", + "caniuse-lite": "^1.0.30001181", "colorette": "^1.2.1", - "electron-to-chromium": "^1.3.634", + "electron-to-chromium": "^1.3.649", "escalade": "^3.1.1", - "node-releases": "^1.1.69" + "node-releases": "^1.1.70" } }, "bser": { @@ -4088,17 +3985,6 @@ "ssri": "^8.0.0", "tar": "^6.0.2", "unique-filename": "^1.1.1" - }, - "dependencies": { - "p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "requires": { - "aggregate-error": "^3.0.0" - } - } } }, "cache-base": { @@ -4141,9 +4027,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001178", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001178.tgz", - "integrity": "sha512-VtdZLC0vsXykKni8Uztx45xynytOi71Ufx9T8kHptSw9AL4dpqailUJJHavttuzUe1KYuBYtChiWv+BAb7mPmQ==", + "version": "1.0.30001191", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001191.tgz", + "integrity": "sha512-xJJqzyd+7GCJXkcoBiQ1GuxEiOBCLQ0aVW9HMekifZsAVGdj5eJ4mFB9fEhSHipq9IOk/QXFJUiIr9lZT+EsGw==", "dev": true }, "capture-exit": { @@ -4203,9 +4089,9 @@ "dev": true }, "chokidar": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.0.tgz", - "integrity": "sha512-JgQM9JS92ZbFR4P90EvmzNpSGhpPBGBSj10PILeDyYFwp4h2/D9OM03wsJ4zW1fEp4ka2DGrnUeD7FuvQ2aZ2Q==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", + "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", "dev": true, "optional": true, "requires": { @@ -4422,12 +4308,6 @@ "wrap-ansi": "^6.2.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -4693,18 +4573,18 @@ "dev": true }, "core-js": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.8.3.tgz", - "integrity": "sha512-KPYXeVZYemC2TkNEkX/01I+7yd+nX3KddKwZ1Ww7SKWdI2wQprSgLmrTddT8nw92AjEklTsPBoSdQBhbI1bQ6Q==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.9.0.tgz", + "integrity": "sha512-PyFBJaLq93FlyYdsndE5VaueA9K5cNB7CGzeCj191YYLhkQM0gdZR2SKihM70oF0wdqKSKClv/tEBOpoRmdOVQ==", "dev": true }, "core-js-compat": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.8.2.tgz", - "integrity": "sha512-LO8uL9lOIyRRrQmZxHZFl1RV+ZbcsAkFWTktn5SmH40WgLtSNYN4m4W2v9ONT147PxBY/XrRhrWq8TlvObyUjQ==", + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.9.0.tgz", + "integrity": "sha512-YK6fwFjCOKWwGnjFUR3c544YsnA/7DoLL0ysncuOJ4pwbriAtOpvM2bygdlcXbvQCQZ7bBU9CL4t7tGl7ETRpQ==", "dev": true, "requires": { - "browserslist": "^4.16.0", + "browserslist": "^4.16.3", "semver": "7.0.0" }, "dependencies": { @@ -5239,23 +5119,23 @@ "dev": true }, "electron-to-chromium": { - "version": "1.3.641", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.641.tgz", - "integrity": "sha512-b0DLhsHSHESC1I+Nx6n4w4Lr61chMd3m/av1rZQhS2IXTzaS5BMM5N+ldWdMIlni9CITMRM09m8He4+YV/92TA==", + "version": "1.3.672", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.672.tgz", + "integrity": "sha512-gFQe7HBb0lbOMqK2GAS5/1F+B0IMdYiAgB9OT/w1F4M7lgJK2aNOMNOM622aEax+nS1cTMytkiT0uMOkbtFmHw==", "dev": true }, "elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz", + "integrity": "sha512-iLhC6ULemrljPZb+QutR5TQGB+pdW6KGD5RSegS+8sorOZT+rdQFbsQFJgvN3eRqNALqJer4oQ16YvJHlU8hzQ==", "requires": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", + "bn.js": "^4.11.9", + "brorand": "^1.1.0", "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" } }, "emittery": { @@ -5354,9 +5234,9 @@ } }, "envinfo": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.3.tgz", - "integrity": "sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA==", + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.4.tgz", + "integrity": "sha512-TQXTYFVVwwluWSFis6K2XKxgrD22jEv0FTuLCQI+OjH7rn93+iY0fSSFM5lrSxFY+H1+B0/cvvlamr3UsBivdQ==", "dev": true }, "errno": { @@ -5549,11 +5429,14 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true + "@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } }, "ansi-styles": { "version": "4.3.0", @@ -5863,9 +5746,9 @@ } }, "eslint-plugin-promise": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.2.1.tgz", - "integrity": "sha512-VoM09vT7bfA7D+upt+FjeBO5eHIJQBUWki1aPvB+vbNiHS3+oGIJGIeyBtKQTME6UPXXy3vV07OL1tHd3ANuDw==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-4.3.1.tgz", + "integrity": "sha512-bY2sGqyptzFBDLh/GMbAxfdJC+b0f23ME63FOE4+Jao0oZ3E1LEwFtWJX/1pGMJLiTtrSSern2CRM/g+dfc0eQ==", "dev": true }, "eslint-scope": { @@ -5979,68 +5862,40 @@ "dev": true }, "ethers": { - "version": "5.0.26", - "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.0.26.tgz", - "integrity": "sha512-MqA8Fvutn3qEW0yBJOHeV6KZmRpF2rqlL2B5058AGkUFsuu6j5Ns/FRlMsbGeQwBz801IB23jQp7vjRfFsKSkg==", - "requires": { - "@ethersproject/abi": "5.0.10", - "@ethersproject/abstract-provider": "5.0.8", - "@ethersproject/abstract-signer": "5.0.11", - "@ethersproject/address": "5.0.9", - "@ethersproject/base64": "5.0.7", - "@ethersproject/basex": "5.0.7", - "@ethersproject/bignumber": "5.0.13", - "@ethersproject/bytes": "5.0.9", - "@ethersproject/constants": "5.0.8", - "@ethersproject/contracts": "5.0.9", - "@ethersproject/hash": "5.0.10", - "@ethersproject/hdnode": "5.0.8", - "@ethersproject/json-wallets": "5.0.10", - "@ethersproject/keccak256": "5.0.7", - "@ethersproject/logger": "5.0.8", - "@ethersproject/networks": "5.0.7", - "@ethersproject/pbkdf2": "5.0.7", - "@ethersproject/properties": "5.0.7", - "@ethersproject/providers": "5.0.19", - "@ethersproject/random": "5.0.7", - "@ethersproject/rlp": "5.0.7", - "@ethersproject/sha2": "5.0.7", - "@ethersproject/signing-key": "5.0.8", - "@ethersproject/solidity": "5.0.8", - "@ethersproject/strings": "5.0.8", - "@ethersproject/transactions": "5.0.9", - "@ethersproject/units": "5.0.9", - "@ethersproject/wallet": "5.0.10", - "@ethersproject/web": "5.0.12", - "@ethersproject/wordlists": "5.0.8" - }, - "dependencies": { - "@ethersproject/abi": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/@ethersproject/abi/-/abi-5.0.10.tgz", - "integrity": "sha512-cfC3lGgotfxX3SMri4+CisOPwignoj/QGHW9J29spC4R4Qqcnk/SYuVkPFBMdLbvBp3f/pGiVqPNwont0TSXhg==", - "requires": { - "@ethersproject/address": "^5.0.9", - "@ethersproject/bignumber": "^5.0.13", - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/constants": "^5.0.8", - "@ethersproject/hash": "^5.0.10", - "@ethersproject/keccak256": "^5.0.7", - "@ethersproject/logger": "^5.0.8", - "@ethersproject/properties": "^5.0.7", - "@ethersproject/strings": "^5.0.8" - } - }, - "@ethersproject/strings": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.0.8.tgz", - "integrity": "sha512-5IsdXf8tMY8QuHl8vTLnk9ehXDDm6x9FB9S9Og5IA1GYhLe5ZewydXSjlJlsqU2t9HRbfv97OJZV/pX8DVA/Hw==", - "requires": { - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/constants": "^5.0.8", - "@ethersproject/logger": "^5.0.8" - } - } + "version": "5.0.31", + "resolved": "https://registry.npmjs.org/ethers/-/ethers-5.0.31.tgz", + "integrity": "sha512-zpq0YbNFLFn+t+ibS8UkVWFeK5w6rVMSvbSHrHAQslfazovLnQ/mc2gdN5+6P45/k8fPgHrfHrYvJ4XvyK/S1A==", + "requires": { + "@ethersproject/abi": "5.0.12", + "@ethersproject/abstract-provider": "5.0.9", + "@ethersproject/abstract-signer": "5.0.13", + "@ethersproject/address": "5.0.10", + "@ethersproject/base64": "5.0.8", + "@ethersproject/basex": "5.0.8", + "@ethersproject/bignumber": "5.0.14", + "@ethersproject/bytes": "5.0.10", + "@ethersproject/constants": "5.0.9", + "@ethersproject/contracts": "5.0.11", + "@ethersproject/hash": "5.0.11", + "@ethersproject/hdnode": "5.0.9", + "@ethersproject/json-wallets": "5.0.11", + "@ethersproject/keccak256": "5.0.8", + "@ethersproject/logger": "5.0.9", + "@ethersproject/networks": "5.0.8", + "@ethersproject/pbkdf2": "5.0.8", + "@ethersproject/properties": "5.0.8", + "@ethersproject/providers": "5.0.23", + "@ethersproject/random": "5.0.8", + "@ethersproject/rlp": "5.0.8", + "@ethersproject/sha2": "5.0.8", + "@ethersproject/signing-key": "5.0.10", + "@ethersproject/solidity": "5.0.9", + "@ethersproject/strings": "5.0.9", + "@ethersproject/transactions": "5.0.10", + "@ethersproject/units": "5.0.10", + "@ethersproject/wallet": "5.0.11", + "@ethersproject/web": "5.0.13", + "@ethersproject/wordlists": "5.0.9" } }, "eventemitter3": { @@ -6538,9 +6393,9 @@ "dev": true }, "file-entry-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.0.tgz", - "integrity": "sha512-fqoO76jZ3ZnYrXLDRxBR1YvOvc0k844kcOg40bgsPrE25LAb/PDqTY+ho64Xh2c8ZXgIKldchCFHczG2UVRcWA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dev": true, "requires": { "flat-cache": "^3.0.4" @@ -6889,9 +6744,9 @@ "dev": true }, "fsevents": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.1.tgz", - "integrity": "sha512-YR47Eg4hChJGAB1O3yEAOkGO+rlzutoICGqGo9EZ4lKWokzZRSyIW1QmTzqjtw8MJdj9srP869CuWw/hyzSiBw==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", "dev": true, "optional": true }, @@ -6969,9 +6824,9 @@ "dev": true }, "get-intrinsic": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz", - "integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", "dev": true, "requires": { "function-bind": "^1.1.1", @@ -7198,9 +7053,9 @@ } }, "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", "dev": true }, "growl": { @@ -7257,6 +7112,14 @@ "dev": true, "requires": { "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } } }, "has-flag": { @@ -7610,9 +7473,9 @@ "dev": true }, "is-callable": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", - "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.3.tgz", + "integrity": "sha512-J1DcMe8UYTBSrKezuIUTUwjXsho29693unXM2YhJUTR2txK/eG47bvNa/wipPFmZFgr/N6f1GA66dv0mEyTIyQ==", "dev": true }, "is-ci": { @@ -7790,11 +7653,12 @@ "dev": true }, "is-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", - "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.2.tgz", + "integrity": "sha512-axvdhb5pdhEVThqJzYXwMlVuZwC+FF2DpcOhTS+y/8jVq4trxyPgfcwIxIKiyeuLlSQYKkmUaPQJ8ZE4yNKXDg==", "dev": true, "requires": { + "call-bind": "^1.0.2", "has-symbols": "^1.0.1" } }, @@ -9626,9 +9490,9 @@ "dev": true }, "json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", "dev": true, "requires": { "minimist": "^1.2.5" @@ -9753,9 +9617,9 @@ } }, "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, "lodash._arraycopy": { @@ -9835,6 +9699,12 @@ "lodash._isiterateecall": "^3.0.0" } }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, "lodash.defaultsdeep": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", @@ -10092,18 +9962,18 @@ "dev": true }, "mime-db": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", - "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", + "version": "1.46.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.46.0.tgz", + "integrity": "sha512-svXaP8UQRZ5K7or+ZmfNhg2xX3yKDMUzqadsSqi4NCH/KomcH75MAMYAGVlvXn4+b/xOPhS3I2uHKRUzvjY7BQ==", "dev": true }, "mime-types": { - "version": "2.1.28", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", - "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", + "version": "2.1.29", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.29.tgz", + "integrity": "sha512-Y/jMt/S5sR9OaqteJtslsFZKWOIIqMACsJSiHghlCAyhf7jfVYjKBmLiX8OgpWeW+fjJ2b+Az69aPFPkUOY6xQ==", "dev": true, "requires": { - "mime-db": "1.45.0" + "mime-db": "1.46.0" } }, "mimic-fn": { @@ -10612,12 +10482,6 @@ "strip-ansi": "^6.0.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "semver": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", @@ -10636,9 +10500,9 @@ } }, "nise": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/nise/-/nise-4.0.4.tgz", - "integrity": "sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz", + "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==", "dev": true, "requires": { "@sinonjs/commons": "^1.7.0", @@ -10817,9 +10681,9 @@ } }, "node-releases": { - "version": "1.1.69", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.69.tgz", - "integrity": "sha512-DGIjo79VDEyAnRlfSqYTsy+yoHd2IOjJiKUozD2MV2D85Vso6Bug56mb9tT/fY5Urt0iqk01H7x+llAruDR2zA==", + "version": "1.1.70", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.70.tgz", + "integrity": "sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==", "dev": true }, "node-status-codes": { @@ -10976,14 +10840,14 @@ } }, "object.getownpropertydescriptors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.1.tgz", - "integrity": "sha512-6DtXgZ/lIZ9hqx4GtZETobXLR/ZLaa0aqV0kzbn80Rf8Z2e/XFnhA0I7p07N2wH8bBBltr2xQPi6sbKWAY2Eng==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.2.tgz", + "integrity": "sha512-WtxeKSzfBjlzL+F9b7M7hewDzMwy+C8NRssHd1YrNlzHzIDrXcXiNOMrezdAEM4UXixgV+vvnyBeN7Rygl2ttQ==", "dev": true, "requires": { - "call-bind": "^1.0.0", + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1" + "es-abstract": "^1.18.0-next.2" } }, "object.pick": { @@ -10996,14 +10860,14 @@ } }, "object.values": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.2.tgz", - "integrity": "sha512-MYC0jvJopr8EK6dPBiO8Nb9mvjdypOachO5REGk6MXzujbBrAisKo3HmdEI6kZDL6fC31Mwee/5YbtMebixeag==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.3.tgz", + "integrity": "sha512-nkF6PfDB9alkOUxpf1HNm/QlkeW3SReqL5WXeBLpEJJnlPSvRaDQpW3gQTksTN3fgJX4hL42RzKyOin6ff3tyw==", "dev": true, "requires": { - "call-bind": "^1.0.0", + "call-bind": "^1.0.2", "define-properties": "^1.1.3", - "es-abstract": "^1.18.0-next.1", + "es-abstract": "^1.18.0-next.2", "has": "^1.0.3" } }, @@ -11077,12 +10941,6 @@ "wcwidth": "^1.0.1" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -11620,12 +11478,6 @@ "react-is": "^17.0.1" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -11836,13 +11688,18 @@ "dev": true }, "pvtsutils": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.1.1.tgz", - "integrity": "sha512-Evbhe6L4Sxwu4SPLQ4LQZhgfWDQO3qa1lju9jM5cxsQp8vE10VipcSmo7hiJW48TmiHgVLgDtC2TL6/+ND+IVg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.1.2.tgz", + "integrity": "sha512-Yfm9Dsk1zfEpOWCaJaHfqtNXAFWNNHMFSCLN6jTnhuCCBCC2nqge4sAgo7UrkRBoAAYIL8TN/6LlLoNfZD/b5A==", "requires": { - "tslib": "^2.0.3" + "tslib": "^2.1.0" } }, + "pvutils": { + "version": "1.0.17", + "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.0.17.tgz", + "integrity": "sha512-wLHYUQxWaXVQvKnwIDWFVKDJku9XDCvyhhxoq8dc5MFdIlRenyPI9eSfEtcvgHgD7FlvCyGAlWgOzRnZD99GZQ==" + }, "qs": { "version": "6.9.6", "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", @@ -11867,9 +11724,9 @@ "dev": true }, "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.0.0.tgz", + "integrity": "sha512-kQ3ACYYIALMi7x8xvMU+qSF6N6baMZms8/q3dn2XFiORhFrLbpR6vTRNGR80HwlakWSoY4RnGoCJWUgnyH6zZQ==" }, "randombytes": { "version": "2.1.0", @@ -12177,9 +12034,9 @@ "dev": true }, "regjsparser": { - "version": "0.6.6", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.6.tgz", - "integrity": "sha512-jjyuCp+IEMIm3N1H1LLTJW1EISEJV9+5oHdEyrt43Pg9cDSb6rrLZei2cVWpl0xTjmmlpec/lEQGYgM7xfpGCQ==", + "version": "0.6.7", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.7.tgz", + "integrity": "sha512-ib77G0uxsA2ovgiYbCVGx4Pv3PSttAx2vIwidqQzbL2U5S4Q+j00HdSAneSBuyVcMvEnTXMjiGgB+DlXozVhpQ==", "dev": true, "requires": { "jsesc": "~0.5.0" @@ -12338,12 +12195,12 @@ "dev": true }, "resolve": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", - "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "dev": true, "requires": { - "is-core-module": "^2.1.0", + "is-core-module": "^2.2.0", "path-parse": "^1.0.6" } }, @@ -12720,9 +12577,9 @@ } }, "sirv": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.10.tgz", - "integrity": "sha512-H5EZCoZaggEUQy8ocKsF7WAToGuZhjJlLvM3XOef46CbdIgbNeQ1p32N1PCuCjkVYwrAVOSMacN6CXXgIzuspg==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.11.tgz", + "integrity": "sha512-SR36i3/LSWja7AJNRBz4fF/Xjpn7lQFI30tZ434dIy+bitLYSP+ZEenHg36i23V2SGEz+kqjksg0uOGZ5LPiqg==", "dev": true, "requires": { "@polka/url": "^1.0.0-next.9", @@ -12731,9 +12588,9 @@ }, "dependencies": { "mime": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.0.tgz", - "integrity": "sha512-ft3WayFSFUVBuJj7BMLKAQcSlItKtfjsKDDsii3rqFDAZ7t11zRe8ASw/GlmivGwVUYtwkQrxiGGpL6gFvB0ag==", + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz", + "integrity": "sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==", "dev": true } } @@ -12990,9 +12847,9 @@ } }, "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", "dev": true }, "spdx-correct": { @@ -13060,9 +12917,9 @@ } }, "ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", "dev": true, "requires": { "minipass": "^3.1.1" @@ -13238,9 +13095,9 @@ } }, "streamr-test-utils": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/streamr-test-utils/-/streamr-test-utils-1.3.0.tgz", - "integrity": "sha512-mSjtHIUyoVksv6OXVDMj5BecbEXCKfXRmSmiHav1kJLEHj4oE9cF1N0Ml1I3KL3ARw51+RloRnPtRSexg71m/Q==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/streamr-test-utils/-/streamr-test-utils-1.3.1.tgz", + "integrity": "sha512-3ol+bRQOSUz6Qjso0/VDBNzTxD9msizCOtsHgx7YqGYkxtIUSlGbsVtZh3JhBnnm53BNyaNHS74+N7Mjoa+w5Q==", "dev": true }, "strict-event-emitter-types": { @@ -13258,12 +13115,6 @@ "strip-ansi": "^6.0.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -13286,12 +13137,6 @@ "strip-ansi": "^6.0.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -13338,6 +13183,14 @@ "dev": true, "requires": { "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } } }, "strip-bom": { @@ -13419,9 +13272,9 @@ }, "dependencies": { "ajv": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.0.tgz", - "integrity": "sha512-svS9uILze/cXbH0z2myCK2Brqprx/+JJYK5pHicT/GQiBfzzhUVAIT6MwqJg8y4xV/zoGsUeuPuwtoiKSGE15g==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-7.1.1.tgz", + "integrity": "sha512-ga/aqDYnUy/o7vbsRTFhhTsNeXiYb5JWDIcRIeZfwRNCefwjNTVYCGdGSUrEmiu3yDK3vFvNbgJxvrQW4JXrYQ==", "dev": true, "requires": { "fast-deep-equal": "^3.1.1", @@ -13490,9 +13343,9 @@ } }, "terser": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.5.1.tgz", - "integrity": "sha512-6VGWZNVP2KTUcltUQJ25TtNjx/XgdDsBDKGt8nN0MpydU36LmbPPcMBd2kmtZNNGVVDLg44k7GKeHHj+4zPIBQ==", + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.6.0.tgz", + "integrity": "sha512-vyqLMoqadC1uR0vywqOZzriDYzgEkNJFK4q9GeyOBHIbiECHiWLKcWfbQWAUaPfxkjDhapSlZB9f7fkMrvkVjA==", "dev": true, "requires": { "commander": "^2.20.0", @@ -13854,9 +13707,10 @@ } }, "typescript": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.4.tgz", - "integrity": "sha512-+Uru0t8qIRgjuCpiSPpfGuhHecMllk5Zsazj5LZvVsEStEjmIRRBZe+jHjGQvsgS7M1wONy2PQXd67EMyV6acg==" + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", + "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", + "dev": true }, "unicode-canonical-property-names-ecmascript": { "version": "1.0.4", @@ -14222,15 +14076,15 @@ } }, "webcrypto-core": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.1.8.tgz", - "integrity": "sha512-hKnFXsqh0VloojNeTfrwFoRM4MnaWzH6vtXcaFcGjPEu+8HmBdQZnps3/2ikOFqS8bJN1RYr6mI2P/FJzyZnXg==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.2.0.tgz", + "integrity": "sha512-p76Z/YLuE4CHCRdc49FB/ETaM4bzM3roqWNJeGs+QNY1fOTzKTOVnhmudW1fuO+5EZg6/4LG9NJ6gaAyxTk9XQ==", "requires": { - "@peculiar/asn1-schema": "^2.0.12", + "@peculiar/asn1-schema": "^2.0.27", "@peculiar/json-schema": "^1.1.12", "asn1js": "^2.0.26", - "pvtsutils": "^1.0.11", - "tslib": "^2.0.1" + "pvtsutils": "^1.1.2", + "tslib": "^2.1.0" } }, "webidl-conversions": { @@ -14575,17 +14429,17 @@ } }, "webpack-cli": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.4.0.tgz", - "integrity": "sha512-/Qh07CXfXEkMu5S8wEpjuaw2Zj/CC0hf/qbTDp6N8N7JjdGuaOjZ7kttz+zhuJO/J5m7alQEhNk9lsc4rC6xgQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.5.0.tgz", + "integrity": "sha512-wXg/ef6Ibstl2f50mnkcHblRPN/P9J4Nlod5Hg9HGFgSeF8rsqDGHJeVe4aR26q9l62TUJi6vmvC2Qz96YJw1Q==", "dev": true, "requires": { "@discoveryjs/json-ext": "^0.5.0", - "@webpack-cli/configtest": "^1.0.0", - "@webpack-cli/info": "^1.2.1", - "@webpack-cli/serve": "^1.2.2", + "@webpack-cli/configtest": "^1.0.1", + "@webpack-cli/info": "^1.2.2", + "@webpack-cli/serve": "^1.3.0", "colorette": "^1.2.1", - "commander": "^6.2.0", + "commander": "^7.0.0", "enquirer": "^2.3.6", "execa": "^5.0.0", "fastest-levenshtein": "^1.0.12", @@ -14597,9 +14451,9 @@ }, "dependencies": { "commander": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", - "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.1.0.tgz", + "integrity": "sha512-pRxBna3MJe6HKnBGsDyMv8ETbptw3axEdYHoqNh7gu5oDcew8fs0xnivZGm06Ogk8zGAJ9VX+OPEr2GXEQK4dg==", "dev": true }, "execa": { @@ -14796,12 +14650,6 @@ "strip-ansi": "^6.0.0" }, "dependencies": { - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -14855,9 +14703,9 @@ } }, "ws": { - "version": "7.4.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", - "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz", + "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==" }, "xml-name-validator": { "version": "3.0.0", diff --git a/package.json b/package.json index 2d16711ae..ee11f4476 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,10 @@ "@babel/preset-typescript": "^7.12.13", "@types/debug": "^4.1.5", "@types/jest": "^26.0.20", + "@types/lodash.uniqueid": "^4.0.6", + "@types/node": "^14.14.29", "@types/qs": "^6.9.5", + "@types/uuid": "^8.3.0", "@typescript-eslint/eslint-plugin": "^4.15.1", "@typescript-eslint/parser": "^4.15.1", "async-mutex": "^0.2.6", @@ -83,6 +86,7 @@ "sinon": "^9.2.4", "streamr-test-utils": "^1.3.0", "terser-webpack-plugin": "^4.2.3", + "typescript": "^4.1.5", "webpack": "^4.46.0", "webpack-bundle-analyzer": "^4.4.0", "webpack-cli": "^4.4.0", @@ -115,11 +119,10 @@ "p-queue": "^6.6.2", "promise-memoize": "^1.2.1", "qs": "^6.9.6", - "quick-lru": "^5.1.1", + "quick-lru": "^6.0.0", "readable-stream": "^3.6.0", "streamr-client-protocol": "^8.0.0-beta.2", "ts-toolbelt": "^9.3.12", - "typescript": "^4.1.4", "uuid": "^8.3.2", "webpack-node-externals": "^2.5.2", "ws": "^7.4.2" From 7eaa363ffa39858c8efaec7627aad4ef9e7a0e6c Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 19 Feb 2021 11:28:35 -0500 Subject: [PATCH 481/517] Fix reversed conditional for 0x prefix in Config.js --- src/Config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Config.ts b/src/Config.ts index eeb9773d9..561a96733 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -147,7 +147,7 @@ export default function ClientConfig(opts: Partial = {}) { if ('privateKey' in options.auth) { const { privateKey } = options.auth - if (typeof privateKey === 'string' && privateKey.startsWith('0x')) { + if (typeof privateKey === 'string' && !privateKey.startsWith('0x')) { options.auth.privateKey = `0x${options.auth!.privateKey}` } } From 695bc221521af9b9e944df57fe03e4cd7b40b414 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 19 Feb 2021 11:29:24 -0500 Subject: [PATCH 482/517] Downgrade quick-lru from 6.0.0 to 5.x due to weird exports syntax in bundle. --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index d1a2e54a7..df563e5b3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11724,9 +11724,9 @@ "dev": true }, "quick-lru": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.0.0.tgz", - "integrity": "sha512-kQ3ACYYIALMi7x8xvMU+qSF6N6baMZms8/q3dn2XFiORhFrLbpR6vTRNGR80HwlakWSoY4RnGoCJWUgnyH6zZQ==" + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" }, "randombytes": { "version": "2.1.0", diff --git a/package.json b/package.json index ee11f4476..06cc87537 100644 --- a/package.json +++ b/package.json @@ -119,7 +119,7 @@ "p-queue": "^6.6.2", "promise-memoize": "^1.2.1", "qs": "^6.9.6", - "quick-lru": "^6.0.0", + "quick-lru": "^5.1.1", "readable-stream": "^3.6.0", "streamr-client-protocol": "^8.0.0-beta.2", "ts-toolbelt": "^9.3.12", From 2a9d769c857f5ca0349c100a6a42047ad630aea1 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 19 Feb 2021 13:44:29 -0500 Subject: [PATCH 483/517] Update to webpack 5. Fix issue with quick-lru@6.x in Jest. --- jest.config.js | 6 +- package-lock.json | 1866 +++++++++++---------------------------------- package.json | 67 +- webpack.config.js | 19 +- 4 files changed, 489 insertions(+), 1469 deletions(-) diff --git a/jest.config.js b/jest.config.js index 4be81d064..abb1179dd 100644 --- a/jest.config.js +++ b/jest.config.js @@ -170,9 +170,9 @@ module.exports = { }, // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation - // transformIgnorePatterns: [ - // "/node_modules/" - // ], + transformIgnorePatterns: [ + '/node_modules/(?!quick-lru)', // quick-lru is esm + ], // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them // unmockedModulePathPatterns: undefined, diff --git a/package-lock.json b/package-lock.json index df563e5b3..653b8258e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -99,6 +99,16 @@ "@babel/types": "^7.12.13" } }, + "@babel/helper-call-delegate": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-call-delegate/-/helper-call-delegate-7.12.13.tgz", + "integrity": "sha512-K1kF0RXK/GpdS9OZDlBllG0+RQQtyzG/TC+nk0VkrUry4l4Xh2T7HdDsDOVlXQY/KcqvE/JQ84pKjKucdrg3FQ==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, "@babel/helper-compilation-targets": { "version": "7.13.0", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.0.tgz", @@ -2233,16 +2243,6 @@ "fastq": "^1.6.0" } }, - "@npmcli/move-file": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", - "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", - "dev": true, - "requires": { - "mkdirp": "^1.0.4", - "rimraf": "^3.0.2" - } - }, "@peculiar/asn1-schema": { "version": "2.0.27", "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.0.27.tgz", @@ -2361,6 +2361,32 @@ "integrity": "sha512-Q1y515GcOdTHgagaVFhHnIFQ38ygs/kmxdNpvpou+raI9UO3YZcHDngBSYKQklcKlvA7iuQlmIKbzvmxcOE9CQ==", "dev": true }, + "@types/eslint": { + "version": "7.2.6", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.6.tgz", + "integrity": "sha512-I+1sYH+NPQ3/tVqCeUSBwTE/0heyvtXqpIopUUArlBm0Kpocb8FbMa3AZ/ASKIFpN3rnEx932TTXDbt9OXsNDw==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz", + "integrity": "sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "0.0.46", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.46.tgz", + "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==", + "dev": true + }, "@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -2598,177 +2624,148 @@ } }, "@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz", + "integrity": "sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg==", "dev": true, "requires": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" + "@webassemblyjs/helper-numbers": "1.11.0", + "@webassemblyjs/helper-wasm-bytecode": "1.11.0" } }, "@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz", + "integrity": "sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA==", "dev": true }, "@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz", + "integrity": "sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w==", "dev": true }, "@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", - "dev": true - }, - "@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", - "dev": true, - "requires": { - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz", + "integrity": "sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA==", "dev": true }, - "@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", + "@webassemblyjs/helper-numbers": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz", + "integrity": "sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0" + "@webassemblyjs/floating-point-hex-parser": "1.11.0", + "@webassemblyjs/helper-api-error": "1.11.0", + "@xtuc/long": "4.2.2" } }, "@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz", + "integrity": "sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA==", "dev": true }, "@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz", + "integrity": "sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" + "@webassemblyjs/ast": "1.11.0", + "@webassemblyjs/helper-buffer": "1.11.0", + "@webassemblyjs/helper-wasm-bytecode": "1.11.0", + "@webassemblyjs/wasm-gen": "1.11.0" } }, "@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz", + "integrity": "sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA==", "dev": true, "requires": { "@xtuc/ieee754": "^1.2.0" } }, "@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.0.tgz", + "integrity": "sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g==", "dev": true, "requires": { "@xtuc/long": "4.2.2" } }, "@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.0.tgz", + "integrity": "sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw==", "dev": true }, "@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz", + "integrity": "sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" + "@webassemblyjs/ast": "1.11.0", + "@webassemblyjs/helper-buffer": "1.11.0", + "@webassemblyjs/helper-wasm-bytecode": "1.11.0", + "@webassemblyjs/helper-wasm-section": "1.11.0", + "@webassemblyjs/wasm-gen": "1.11.0", + "@webassemblyjs/wasm-opt": "1.11.0", + "@webassemblyjs/wasm-parser": "1.11.0", + "@webassemblyjs/wast-printer": "1.11.0" } }, "@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz", + "integrity": "sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" + "@webassemblyjs/ast": "1.11.0", + "@webassemblyjs/helper-wasm-bytecode": "1.11.0", + "@webassemblyjs/ieee754": "1.11.0", + "@webassemblyjs/leb128": "1.11.0", + "@webassemblyjs/utf8": "1.11.0" } }, "@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz", + "integrity": "sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" + "@webassemblyjs/ast": "1.11.0", + "@webassemblyjs/helper-buffer": "1.11.0", + "@webassemblyjs/wasm-gen": "1.11.0", + "@webassemblyjs/wasm-parser": "1.11.0" } }, "@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz", + "integrity": "sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" + "@webassemblyjs/ast": "1.11.0", + "@webassemblyjs/helper-api-error": "1.11.0", + "@webassemblyjs/helper-wasm-bytecode": "1.11.0", + "@webassemblyjs/ieee754": "1.11.0", + "@webassemblyjs/leb128": "1.11.0", + "@webassemblyjs/utf8": "1.11.0" } }, "@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz", + "integrity": "sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ==", "dev": true, "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", + "@webassemblyjs/ast": "1.11.0", "@xtuc/long": "4.2.2" } }, @@ -2891,12 +2888,6 @@ "uri-js": "^4.2.2" } }, - "ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true - }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -2962,12 +2953,6 @@ } } }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -2995,6 +2980,12 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, + "array-filter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-1.0.0.tgz", + "integrity": "sha1-uveeYubvTCpMC4MSMtr/7CUfnYM=", + "dev": true + }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -3066,33 +3057,6 @@ "pvutils": "^1.0.17" } }, - "assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "requires": { - "object-assign": "^4.1.1", - "util": "0.10.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "requires": { - "inherits": "2.0.1" - } - } - } - }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", @@ -3134,12 +3098,12 @@ "optional": true }, "async-mutex": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.6.tgz", - "integrity": "sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw==", + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.1.tgz", + "integrity": "sha512-vRfQwcqBnJTLzVQo72Sf7KIUbcSUP5hNchx6udI1U6LuPQpfePgdjJzlCe76yFZ8pxlLjn9lwcl/Ya0TSOv0Tw==", "dev": true, "requires": { - "tslib": "^2.0.0" + "tslib": "^2.1.0" } }, "asynckit": { @@ -3154,6 +3118,15 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "available-typed-arrays": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", + "integrity": "sha512-XWX3OX8Onv97LMk/ftVyBibpGwY5a8SmuxZPzeOxqmuEqUCOM9ZE+uIaD1VNJ5QnvU2UQusvmKbuM1FR8QWGfQ==", + "dev": true, + "requires": { + "array-filter": "^1.0.0" + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -3685,16 +3658,6 @@ "dev": true, "optional": true }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -3883,15 +3846,6 @@ } } }, - "browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "requires": { - "pako": "~1.0.5" - } - }, "browserslist": { "version": "4.16.3", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz", @@ -3950,43 +3904,12 @@ "node-gyp-build": "^4.2.0" } }, - "builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, "bytes": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", "dev": true }, - "cacache": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", - "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", - "dev": true, - "requires": { - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.0", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - } - }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", @@ -4412,68 +4335,12 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "confusing-browser-globals": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.10.tgz", "integrity": "sha512-gNld/3lySHwuhaVluJUKLePYirM3QNCKzVxqAdhJII9/WXKVX5PURzMVJspS1jTslSqjeuG4KMVTSouit5YPHA==", "dev": true }, - "console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, "contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", @@ -4532,40 +4399,6 @@ "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "dev": true }, - "copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", @@ -4706,12 +4539,6 @@ } } }, - "cyclist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", - "dev": true - }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -4976,12 +4803,6 @@ "esutils": "^2.0.2" } }, - "domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true - }, "domexception": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", @@ -5052,50 +4873,6 @@ } } }, - "duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -5180,48 +4957,6 @@ "graceful-fs": "^4.1.2", "memory-fs": "^0.5.0", "tapable": "^1.0.0" - }, - "dependencies": { - "memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "requires": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } } }, "enquirer": { @@ -5279,6 +5014,12 @@ "string.prototype.trimstart": "^1.0.3" } }, + "es-module-lexer": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.0.tgz", + "integrity": "sha512-iuEGihqqhKWFgh72Q/Jtch7V2t/ft8w8IPP2aEN8ArYKO+IWyo6hsi96hCdgyeEDQIV3InhYQ9BlwUFPGXrbEQ==", + "dev": true + }, "es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -6386,12 +6127,6 @@ "pend": "~1.2.0" } }, - "figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", - "dev": true - }, "file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -6533,48 +6268,6 @@ "integrity": "sha512-zAoAQiudy+r5SvnSw3KJy5os/oRJYHzrzja/tBDqrZtNhUw8bt6y8OBzMWcjWr+8liV8Eb6yOhw8WZ7VFZ5ZzA==", "dev": true }, - "flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "follow-redirects": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.2.tgz", @@ -6587,6 +6280,12 @@ "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, + "foreach": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", + "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", + "dev": true + }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", @@ -6625,48 +6324,6 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", "dev": true }, - "from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "fs-extra": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", @@ -6693,50 +6350,6 @@ "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", "dev": true }, - "fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -6968,6 +6581,12 @@ } } }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, "globals": { "version": "11.12.0", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", @@ -7296,12 +6915,6 @@ "sshpk": "^1.7.0" } }, - "https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, "https-proxy-agent": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", @@ -7332,12 +6945,6 @@ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, - "iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, "ignore": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", @@ -7376,12 +6983,6 @@ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", "dev": true }, - "infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -7450,6 +7051,15 @@ } } }, + "is-arguments": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.0.tgz", + "integrity": "sha512-1Ij4lOMPl/xB5kBDn7I+b2ttPMKa8szhEIrXDuXQD/oe3HJLTLhqhgGspwgyGd6MOywBUqVvYicF72lkgDnIHg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0" + } + }, "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -7572,6 +7182,12 @@ "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", "dev": true }, + "is-generator-function": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.8.tgz", + "integrity": "sha512-2Omr/twNtufVZFr1GhxjOMFPAj2sjc/dKaIqBhvo4qciXfJmITGH6ZGd8eZYNHza8t1y0e01AuqRhJwfWp26WQ==", + "dev": true + }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", @@ -7689,6 +7305,19 @@ "has-symbols": "^1.0.1" } }, + "is-typed-array": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.5.tgz", + "integrity": "sha512-S+GRDgJlR3PyEbsX/Fobd9cqpZBuvUS+8asRqYDMLCb2qMzt1oz5m5oxQCxOgUDxiWsOVNi4yaF+/uvdlHlYug==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.2", + "call-bind": "^1.0.2", + "es-abstract": "^1.18.0-next.2", + "foreach": "^2.0.5", + "has-symbols": "^1.0.1" + } + }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -9580,9 +9209,9 @@ } }, "loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", + "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", "dev": true }, "loader-utils": { @@ -9859,9 +9488,9 @@ } }, "memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", + "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", "dev": true, "requires": { "errno": "^0.1.3", @@ -10015,33 +9644,6 @@ "yallist": "^4.0.0" } }, - "minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, - "minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "requires": { - "minipass": "^3.0.0" - } - }, "minizlib": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", @@ -10052,24 +9654,6 @@ "yallist": "^4.0.0" } }, - "mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "requires": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - } - }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -10356,40 +9940,6 @@ } } }, - "move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "requires": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" - } - } - } - }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -10565,88 +10115,6 @@ "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", "dev": true }, - "node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "requires": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - }, - "dependencies": { - "buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "requires": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - }, - "dependencies": { - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, "node-modules-regexp": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", @@ -11062,12 +10530,6 @@ } } }, - "os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -11225,59 +10687,10 @@ "thunkify": "^2.1.2" } }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "dev": true, - "requires": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, "requires": { "callsites": "^3.0.0" @@ -11323,12 +10736,6 @@ "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", "dev": true }, - "path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true - }, "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", @@ -11522,12 +10929,6 @@ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, - "promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, "promise-memoize": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/promise-memoize/-/promise-memoize-1.2.1.tgz", @@ -11658,29 +11059,6 @@ "once": "^1.3.1" } }, - "pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "requires": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - }, - "dependencies": { - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - } - } - }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -11705,18 +11083,6 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz", "integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ==" }, - "querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true - }, - "querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true - }, "queue-microtask": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.2.tgz", @@ -11724,9 +11090,9 @@ "dev": true }, "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-6.0.0.tgz", + "integrity": "sha512-kQ3ACYYIALMi7x8xvMU+qSF6N6baMZms8/q3dn2XFiORhFrLbpR6vTRNGR80HwlakWSoY4RnGoCJWUgnyH6zZQ==" }, "randombytes": { "version": "2.1.0", @@ -12289,15 +11655,6 @@ "queue-microtask": "^1.2.2" } }, - "run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, - "requires": { - "aproba": "^1.1.1" - } - }, "safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -12472,12 +11829,6 @@ } } }, - "setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, "setprototypeof": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", @@ -12916,15 +12267,6 @@ "tweetnacl": "~0.14.0" } }, - "ssri": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", - "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", - "dev": true, - "requires": { - "minipass": "^3.1.1" - } - }, "stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", @@ -12975,109 +12317,6 @@ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", "dev": true }, - "stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "requires": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "requires": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, "streamr-client-protocol": { "version": "8.0.0-beta.2", "resolved": "https://registry.npmjs.org/streamr-client-protocol/-/streamr-client-protocol-8.0.0-beta.2.tgz", @@ -13368,20 +12607,17 @@ } }, "terser-webpack-plugin": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz", - "integrity": "sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.1.tgz", + "integrity": "sha512-5XNNXZiR8YO6X6KhSGXfY0QrGrCRlSwAEjIIrlRQR4W8nP69TaJUlh3bkuac6zzgspiGPfKEHcY295MMVExl5Q==", "dev": true, "requires": { - "cacache": "^15.0.5", - "find-cache-dir": "^3.3.1", - "jest-worker": "^26.5.0", - "p-limit": "^3.0.2", + "jest-worker": "^26.6.2", + "p-limit": "^3.1.0", "schema-utils": "^3.0.0", "serialize-javascript": "^5.0.1", "source-map": "^0.6.1", - "terser": "^5.3.4", - "webpack-sources": "^1.4.3" + "terser": "^5.5.1" }, "dependencies": { "schema-utils": { @@ -13426,48 +12662,6 @@ "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", "dev": true }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "thunkify": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz", @@ -13480,27 +12674,12 @@ "integrity": "sha1-84sK6B03R9YoAB9B2vxlKs5nHAo=", "dev": true }, - "timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dev": true, - "requires": { - "setimmediate": "^1.0.4" - } - }, "tmpl": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", "dev": true }, - "to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -13589,61 +12768,182 @@ "punycode": "^2.1.1" } }, - "ts-toolbelt": { - "version": "9.3.12", - "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.3.12.tgz", - "integrity": "sha512-xxvVS/vhnuiBnOplvkKQe4Npp+KvClBCf62v3LqEOMzcOL/6V8eEIqhNHm+dJQq5Obvx6e87eHe56yapW73xSQ==" - }, - "tsconfig-paths": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", - "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "ts-loader": { + "version": "8.0.17", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.17.tgz", + "integrity": "sha512-OeVfSshx6ot/TCxRwpBHQ/4lRzfgyTkvi7ghDVrLXOHzTbSK413ROgu/xNqM72i3AFeAIJgQy78FwSMKmOW68w==", "dev": true, "requires": { - "@types/json5": "^0.0.29", - "json5": "^1.0.1", - "minimist": "^1.2.0", - "strip-bom": "^3.0.0" + "chalk": "^4.1.0", + "enhanced-resolve": "^4.0.0", + "loader-utils": "^2.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" }, "dependencies": { - "json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { - "minimist": "^1.2.0" + "color-convert": "^2.0.1" } - } - } - }, - "tslib": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", - "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" - }, - "tsutils": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz", - "integrity": "sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==", - "dev": true, - "requires": { - "tslib": "^1.8.1" - }, - "dependencies": { - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "micromatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", + "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.0.5" + } + }, + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } } } }, - "tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true + "ts-toolbelt": { + "version": "9.3.12", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-9.3.12.tgz", + "integrity": "sha512-xxvVS/vhnuiBnOplvkKQe4Npp+KvClBCf62v3LqEOMzcOL/6V8eEIqhNHm+dJQq5Obvx6e87eHe56yapW73xSQ==" + }, + "tsconfig-paths": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz", + "integrity": "sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw==", + "dev": true, + "requires": { + "@types/json5": "^0.0.29", + "json5": "^1.0.1", + "minimist": "^1.2.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "json5": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } + } + } + }, + "tslib": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", + "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" + }, + "tsutils": { + "version": "3.20.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz", + "integrity": "sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + }, + "dependencies": { + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + } + } }, "tunnel-agent": { "version": "0.6.0", @@ -13691,12 +12991,6 @@ "mime-types": "~2.1.24" } }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, "typedarray-to-buffer": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", @@ -13752,24 +13046,6 @@ "set-value": "^2.0.1" } }, - "unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "requires": { - "unique-slug": "^2.0.0" - } - }, - "unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "requires": { - "imurmurhash": "^0.1.4" - } - }, "universalify": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", @@ -13850,24 +13126,6 @@ "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", "dev": true }, - "url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "requires": { - "punycode": "1.3.2", - "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } - } - }, "url-parse-lax": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", @@ -13893,20 +13151,17 @@ } }, "util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", + "version": "0.12.3", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.3.tgz", + "integrity": "sha512-I8XkoQwE+fPQEhy9v012V+TSdH2kp9ts29i20TaaDUXsg7x/onePbhFJUExBfv/2ay1ZOp/Vsm3nDlmnFGSAog==", "dev": true, "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - } + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "safe-buffer": "^5.1.2", + "which-typed-array": "^1.1.2" } }, "util-deprecate": { @@ -13977,12 +13232,6 @@ "extsprintf": "^1.2.0" } }, - "vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, "w3c-hr-time": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", @@ -14011,59 +13260,13 @@ } }, "watchpack": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.5.tgz", - "integrity": "sha512-9P3MWk6SrKjHsGkLT2KHXdQ/9SNkyoJbabxnKOoJepsvJjJG8uYTR3yTPxPQvNDI3w4Nz1xnE0TLHK4RIVe/MQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.1.1.tgz", + "integrity": "sha512-Oo7LXCmc1eE1AjyuSBmtC3+Wy4HcV8PxWh2kP6fOl8yTlNS7r0K9l1ao2lrrUza7V39Y3D/BbJgY8VeSlc5JKw==", "dev": true, "requires": { - "chokidar": "^3.4.1", - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0", - "watchpack-chokidar2": "^2.0.1" - } - }, - "watchpack-chokidar2": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.1.tgz", - "integrity": "sha512-nCFfBIPKr5Sh61s4LPpy1Wtfi0HE8isJ3d2Yb5/Ppw2P2B/3eVSEBjKfN0fmHJSK14+31KwMKmcrzs2GM4P0Ww==", - "dev": true, - "optional": true, - "requires": { - "chokidar": "^2.1.8" - }, - "dependencies": { - "chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "optional": true, - "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true, - "requires": { - "bindings": "^1.5.0", - "nan": "^2.12.1" - } - } + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" } }, "wcwidth": { @@ -14094,250 +13297,67 @@ "dev": true }, "webpack": { - "version": "4.46.0", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.46.0.tgz", - "integrity": "sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==", - "dev": true, - "requires": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.4.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", + "version": "5.24.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.24.0.tgz", + "integrity": "sha512-ZkDxabL/InAQy9jluQTA8VIB7Gkhsv5uMJdAIz4QP2u4zaOX6+Tig7Jv+WSwhHp9qTnAx0rmn0dVTUPqZGRLbg==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.0", + "@types/estree": "^0.0.46", + "@webassemblyjs/ast": "1.11.0", + "@webassemblyjs/wasm-edit": "1.11.0", + "@webassemblyjs/wasm-parser": "1.11.0", + "acorn": "^8.0.4", + "browserslist": "^4.14.5", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.5.0", - "eslint-scope": "^4.0.3", + "enhanced-resolve": "^5.7.0", + "es-module-lexer": "^0.4.0", + "eslint-scope": "^5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.4", "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.3", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", - "webpack-sources": "^1.4.1" + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.0.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.1", + "watchpack": "^2.0.0", + "webpack-sources": "^2.1.1" }, "dependencies": { "acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true - }, - "cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, - "requires": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "requires": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "requires": { - "locate-path": "^3.0.0" - } - }, - "is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "requires": { - "yallist": "^3.0.2" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "requires": { - "p-limit": "^2.0.0" - } - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.5.tgz", + "integrity": "sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg==", "dev": true }, - "pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "enhanced-resolve": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.7.0.tgz", + "integrity": "sha512-6njwt/NsZFUKhM6j9U8hzVyD4E4r0x7NQzhTCbcWOJ0IQjNSAoalWmb0AE51Wn+fwan5qVESWi7t2ToBxs9vrw==", "dev": true, "requires": { - "find-up": "^3.0.0" - } - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "requires": { - "glob": "^7.1.3" + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" } }, "schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "requires": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - } - }, - "serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "dev": true, - "requires": { - "figgy-pudding": "^3.5.1" - } - }, - "terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "dev": true, - "requires": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - } - }, - "terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "dev": true, "requires": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } }, - "yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "tapable": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", + "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", "dev": true } } @@ -14518,13 +13538,13 @@ "integrity": "sha512-aHdl/y2N7PW2Sx7K+r3AxpJO+aDMcYzMQd60Qxefq3+EwhewSbTBqNumOsCE1JsCUNoyfGj5465N0sSf6hc/5w==" }, "webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.2.0.tgz", + "integrity": "sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==", "dev": true, "requires": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" }, "dependencies": { "source-map": { @@ -14576,6 +13596,21 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "which-typed-array": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.4.tgz", + "integrity": "sha512-49E0SpUe90cjpoc7BOJwyPHRqSAd12c10Qm2amdEZrJPCY2NDxaW01zHITrem+rnETY3dwrbH3UUrUwagfCYDA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.2", + "call-bind": "^1.0.0", + "es-abstract": "^1.18.0-next.1", + "foreach": "^2.0.5", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.1", + "is-typed-array": "^1.1.3" + } + }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", @@ -14630,15 +13665,6 @@ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", "dev": true }, - "worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "dev": true, - "requires": { - "errno": "~0.1.7" - } - }, "wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -14725,12 +13751,6 @@ "integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=", "dev": true }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true - }, "y18n": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", diff --git a/package.json b/package.json index 06cc87537..28cd6f746 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,6 @@ "type": "git", "url": "git://github.com/streamr-dev/streamr-client.git" }, - "main": "dist/streamr-client.nodejs.js", - "browser": "dist/streamr-client.web.min.js", "types": "dist/types/src/StreamrClient.d.ts", "directories": { "example": "examples", @@ -46,66 +44,71 @@ "author": "Streamr", "license": "Apache-2.0", "devDependencies": { - "@babel/cli": "^7.12.10", - "@babel/core": "^7.12.10", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/plugin-transform-classes": "^7.12.1", - "@babel/plugin-transform-modules-commonjs": "^7.12.1", - "@babel/plugin-transform-runtime": "^7.12.15", - "@babel/preset-env": "^7.12.11", - "@babel/preset-typescript": "^7.12.13", + "@babel/cli": "^7.12.17", + "@babel/core": "^7.12.17", + "@babel/helper-call-delegate": "^7.12.13", + "@babel/plugin-proposal-class-properties": "^7.12.13", + "@babel/plugin-transform-classes": "^7.12.13", + "@babel/plugin-transform-modules-commonjs": "^7.12.13", + "@babel/plugin-transform-runtime": "^7.12.17", + "@babel/preset-env": "^7.12.17", + "@babel/preset-typescript": "^7.12.17", "@types/debug": "^4.1.5", "@types/jest": "^26.0.20", "@types/lodash.uniqueid": "^4.0.6", - "@types/node": "^14.14.29", + "@types/node": "^14.14.31", "@types/qs": "^6.9.5", "@types/uuid": "^8.3.0", "@typescript-eslint/eslint-plugin": "^4.15.1", "@typescript-eslint/parser": "^4.15.1", - "async-mutex": "^0.2.6", + "async-mutex": "^0.3.0", "babel-loader": "^8.2.2", "babel-plugin-add-module-exports": "^1.0.4", "babel-plugin-transform-class-properties": "^6.24.1", "benchmark": "^2.1.4", "buffer": "^6.0.3", "chromedriver": "^88.0.0", - "core-js": "^3.8.3", + "core-js": "^3.9.0", + "crypto-browserify": "^3.12.0", "eslint": "^7.20.0", "eslint-config-airbnb": "^18.2.1", "eslint-config-streamr-nodejs": "^1.3.0", "eslint-loader": "^4.0.2", "eslint-plugin-import": "^2.22.1", - "eslint-plugin-promise": "^4.2.1", - "ethers": "^5.0.26", + "eslint-plugin-promise": "^4.3.1", + "ethers": "^5.0.31", "express": "^4.17.1", "geckodriver": "^1.22.1", "git-revision-webpack-plugin": "^3.0.6", "jest": "^26.6.3", "jest-circus": "^26.6.3", "nightwatch": "^1.5.1", + "process": "^0.11.10", "sinon": "^9.2.4", - "streamr-test-utils": "^1.3.0", - "terser-webpack-plugin": "^4.2.3", + "streamr-test-utils": "^1.3.1", + "terser-webpack-plugin": "^5.1.1", + "ts-loader": "^8.0.17", "typescript": "^4.1.5", - "webpack": "^4.46.0", + "util": "^0.12.3", + "webpack": "^5.23.0", "webpack-bundle-analyzer": "^4.4.0", - "webpack-cli": "^4.4.0", + "webpack-cli": "^4.5.0", "webpack-merge": "^5.7.3" }, "#IMPORTANT": "babel-runtime must be in dependencies, not devDependencies", "dependencies": { + "@babel/runtime": "^7.12.18", + "@babel/runtime-corejs3": "^7.12.18", "@ethersproject/abi": "^5.0.12", - "@babel/runtime": "^7.12.13", - "@babel/runtime-corejs3": "^7.12.13", - "@ethersproject/address": "^5.0.9", - "@ethersproject/bignumber": "^5.0.13", - "@ethersproject/bytes": "^5.0.9", - "@ethersproject/contracts": "^5.0.9", - "@ethersproject/keccak256": "^5.0.7", - "@ethersproject/providers": "^5.0.19", - "@ethersproject/sha2": "^5.0.7", - "@ethersproject/transactions": "^5.0.9", - "@ethersproject/wallet": "^5.0.10", + "@ethersproject/address": "^5.0.10", + "@ethersproject/bignumber": "^5.0.14", + "@ethersproject/bytes": "^5.0.10", + "@ethersproject/contracts": "^5.0.11", + "@ethersproject/keccak256": "^5.0.8", + "@ethersproject/providers": "^5.0.23", + "@ethersproject/sha2": "^5.0.8", + "@ethersproject/transactions": "^5.0.10", + "@ethersproject/wallet": "^5.0.11", "debug": "^4.3.2", "eventemitter3": "^4.0.7", "lodash.uniqueid": "^4.0.1", @@ -119,13 +122,13 @@ "p-queue": "^6.6.2", "promise-memoize": "^1.2.1", "qs": "^6.9.6", - "quick-lru": "^5.1.1", + "quick-lru": "^6.0.0", "readable-stream": "^3.6.0", "streamr-client-protocol": "^8.0.0-beta.2", "ts-toolbelt": "^9.3.12", "uuid": "^8.3.2", "webpack-node-externals": "^2.5.2", - "ws": "^7.4.2" + "ws": "^7.4.3" }, "optionalDependencies": { "bufferutil": "^4.0.3", diff --git a/webpack.config.js b/webpack.config.js index 62fc18c5d..4733b4087 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -28,11 +28,6 @@ module.exports = (env, argv) => { entry: path.join(__dirname, 'src', 'StreamrClient.ts'), devtool: 'source-map', output: { - path: path.join(__dirname, 'dist'), - library: { - root: 'StreamrClient', - amd: libraryName, - }, umdNamedDefine: true, }, optimization: { @@ -126,16 +121,14 @@ module.exports = (env, argv) => { libraryExport: 'default', // This fixes the above. library: 'StreamrClient', }, - node: { - stream: true, - buffer: true, - }, resolve: { alias: { stream: 'readable-stream', + util: 'util', http: path.resolve(__dirname, './src/shim/http-https.js'), https: path.resolve(__dirname, './src/shim/http-https.js'), ws: path.resolve(__dirname, './src/shim/ws.js'), + crypto: path.resolve(__dirname, 'node_modules', 'crypto-browserify'), buffer: path.resolve(__dirname, 'node_modules', 'buffer'), 'node-fetch': path.resolve(__dirname, './src/shim/node-fetch.js'), 'node-webcrypto-ossl': path.resolve(__dirname, 'src/shim/crypto.js'), @@ -143,6 +136,10 @@ module.exports = (env, argv) => { } }, plugins: [ + new webpack.ProvidePlugin({ + process: 'process/browser', + Buffer: ['buffer', 'Buffer'], + }), ...(analyze ? [ new BundleAnalyzerPlugin({ analyzerMode: 'static', @@ -153,7 +150,7 @@ module.exports = (env, argv) => { ] }) - let clientMinifiedConfig = {} + let clientMinifiedConfig if (isProduction) { clientMinifiedConfig = merge({}, clientConfig, { @@ -180,5 +177,5 @@ module.exports = (env, argv) => { }) } - return [serverConfig, clientConfig, clientMinifiedConfig] + return [serverConfig, clientConfig, clientMinifiedConfig].filter(Boolean) } From ba9905eeaff7b0ca4f7fce4d7909492c393d356c Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Fri, 19 Feb 2021 13:49:33 -0500 Subject: [PATCH 484/517] Fix production build. --- package.json | 2 ++ webpack.config.js | 4 +--- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 28cd6f746..0ec2da2ed 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,8 @@ "url": "git://github.com/streamr-dev/streamr-client.git" }, "types": "dist/types/src/StreamrClient.d.ts", + "main": "dist/streamr-client.nodejs.js", + "browser": "dist/streamr-client.web.min.js", "directories": { "example": "examples", "test": "test" diff --git a/webpack.config.js b/webpack.config.js index 4733b4087..763ad6ee0 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -159,11 +159,9 @@ module.exports = (env, argv) => { minimize: true, minimizer: [ new TerserPlugin({ - cache: true, parallel: true, - sourceMap: true, terserOptions: { - ecma: 2015, + ecma: 2018, output: { comments: false, }, From 6dc86e56ff799bc95fb606104273a1e069a47d12 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 22 Feb 2021 11:10:11 -0500 Subject: [PATCH 485/517] Add AggregatedError.test.ts. Add ts-jest. --- jest.config.js | 2 +- package-lock.json | 51 ++++++++++ package.json | 1 + src/utils/AggregatedError.ts | 37 +++++--- test/unit/AggregatedError.test.ts | 151 ++++++++++++++++++++++++++++++ tsconfig.json | 3 + 6 files changed, 232 insertions(+), 13 deletions(-) create mode 100644 test/unit/AggregatedError.test.ts diff --git a/jest.config.js b/jest.config.js index abb1179dd..ce4817842 100644 --- a/jest.config.js +++ b/jest.config.js @@ -85,7 +85,7 @@ module.exports = { // notifyMode: "always", // A preset that is used as a base for Jest's configuration - // preset: null, + preset: 'ts-jest', // Run tests from one or more projects // projects: null, diff --git a/package-lock.json b/package-lock.json index 653b8258e..805c5d3ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3859,6 +3859,15 @@ "node-releases": "^1.1.70" } }, + "bs-logger": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/bs-logger/-/bs-logger-0.2.6.tgz", + "integrity": "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==", + "dev": true, + "requires": { + "fast-json-stable-stringify": "2.x" + } + }, "bser": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", @@ -9429,6 +9438,12 @@ "semver": "^5.6.0" } }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, "makeerror": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", @@ -12768,6 +12783,42 @@ "punycode": "^2.1.1" } }, + "ts-jest": { + "version": "26.5.1", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-26.5.1.tgz", + "integrity": "sha512-G7Rmo3OJMvlqE79amJX8VJKDiRcd7/r61wh9fnvvG8cAjhA9edklGw/dCxRSQmfZ/z8NDums5srSVgwZos1qfg==", + "dev": true, + "requires": { + "@types/jest": "26.x", + "bs-logger": "0.x", + "buffer-from": "1.x", + "fast-json-stable-stringify": "2.x", + "jest-util": "^26.1.0", + "json5": "2.x", + "lodash": "4.x", + "make-error": "1.x", + "mkdirp": "1.x", + "semver": "7.x", + "yargs-parser": "20.x" + }, + "dependencies": { + "semver": { + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "yargs-parser": { + "version": "20.2.6", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.6.tgz", + "integrity": "sha512-AP1+fQIWSM/sMiET8fyayjx/J+JmTPt2Mr0FkrgqB4todtfa53sOsrSAcIrJRD5XS20bKUwaDIuMkWKCEiQLKA==", + "dev": true + } + } + }, "ts-loader": { "version": "8.0.17", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-8.0.17.tgz", diff --git a/package.json b/package.json index 0ec2da2ed..e09c406d7 100644 --- a/package.json +++ b/package.json @@ -89,6 +89,7 @@ "sinon": "^9.2.4", "streamr-test-utils": "^1.3.1", "terser-webpack-plugin": "^5.1.1", + "ts-jest": "^26.5.1", "ts-loader": "^8.0.17", "typescript": "^4.1.5", "util": "^0.12.3", diff --git a/src/utils/AggregatedError.ts b/src/utils/AggregatedError.ts index 735f9d6da..a68c5b942 100644 --- a/src/utils/AggregatedError.ts +++ b/src/utils/AggregatedError.ts @@ -7,10 +7,20 @@ * See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AggregateError */ +function joinMessages(msgs: (string | undefined)[]): string { + return msgs.filter(Boolean).join('\n') +} + export default class AggregatedError extends Error { errors: Set + ownMessage?: string constructor(errors: Error[] = [], errorMessage = '') { - super(errorMessage) + const message = joinMessages([ + errorMessage, + ...errors.map((err) => err.message) + ]) + super(message) + this.ownMessage = errorMessage this.errors = new Set(errors) if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor) @@ -26,7 +36,7 @@ export default class AggregatedError extends Error { return undefined } - return new AggregatedError(errs, errorMessage) + return new this(errs, errorMessage) } /** @@ -43,38 +53,41 @@ export default class AggregatedError extends Error { * Handles 'upgrading' an existing error to an AggregatedError when necesary. */ static from(oldErr?: Error | AggregatedError, newErr?: Error, msg?: string) { - if (newErr && msg) { - // copy message - newErr.message = `${msg}: ${newErr.message}` // eslint-disable-line no-param-reassign - } - if (!newErr) { + if (oldErr && msg) { + // copy message + oldErr.message = joinMessages([oldErr.message, msg]) // eslint-disable-line no-param-reassign + } return oldErr } // When no oldErr, just return newErr if (!oldErr) { + if (newErr && msg) { + // copy message + newErr.message = joinMessages([newErr.message, msg]) // eslint-disable-line no-param-reassign + } return newErr } // When oldErr is an AggregatedError, extend it if (oldErr instanceof AggregatedError) { - return oldErr.extend(newErr, msg || newErr.message) + return oldErr.extend(newErr, msg, this) } // Otherwise create new AggregatedError from oldErr and newErr - return new AggregatedError([oldErr, newErr], msg || newErr.message) + return new this([oldErr]).extend(newErr, msg) } /** * Create a new error that adds err to list of errors */ - extend(err: Error, message = ''): AggregatedError { + extend(err: Error, message = '', baseClass = this.constructor): AggregatedError { if (err === this || this.errors.has(err)) { return this } - - return new AggregatedError([err, ...this.errors], [message, this.message || ''].join('\n')) + const errors = [err, ...this.errors] + return new ( baseClass)(errors, joinMessages([message, this.ownMessage])) } } diff --git a/test/unit/AggregatedError.test.ts b/test/unit/AggregatedError.test.ts new file mode 100644 index 000000000..3eeb9776b --- /dev/null +++ b/test/unit/AggregatedError.test.ts @@ -0,0 +1,151 @@ +import AggregatedError from '../../src/utils/AggregatedError' + +describe('AggregatedError', () => { + describe('new', () => { + it('works without args', () => { + const err = new AggregatedError() + expect(err.message).toBe('') + expect(err.stack).not.toBe('') + expect(err.errors).toEqual(new Set()) + }) + + it('can take subError', () => { + const subError = new Error('test') + const err = new AggregatedError([subError]) + expect(err.message).toContain(subError.message) + expect(err.stack).not.toBe('') + expect(err.errors).toEqual(new Set([subError])) + }) + + it('can take custom message', () => { + const subError = new Error('test') + const customMessage = 'customMessage' + const err = new AggregatedError([subError], customMessage) + expect(err.message).toContain(subError.message) + expect(err.message).toContain(customMessage) + expect(err.stack).not.toBe('') + expect(err.errors).toEqual(new Set([subError])) + }) + + it('works without Errors', () => { + const customMessage = 'customMessage' + const err = new AggregatedError([], customMessage) + expect(err.message).toContain(customMessage) + expect(err.stack).not.toBe('') + expect(err.errors).toEqual(new Set([])) + }) + }) + + describe('extend', () => { + it('can extend from another error', () => { + const subError1 = new Error('subError1') + const subError2 = new Error('subError2') + const err = new AggregatedError([subError1]).extend(subError2) + expect(err.message).toContain(subError1.message) + expect(err.message).toContain(subError2.message) + expect(err.stack).not.toBe('') + expect(err.errors).toEqual(new Set([subError1, subError2])) + }) + + it('can extend from another error and custom message', () => { + const subError1 = new Error('subError1') + const subError2 = new Error('subError2') + const customMessage = 'customMessage' + const err = new AggregatedError([subError1]).extend(subError2, customMessage) + expect(err.message).toContain(subError1.message) + expect(err.message).toContain(subError2.message) + expect(err.message).toContain(customMessage) + expect(err.stack).not.toBe('') + expect(err.errors).toEqual(new Set([subError1, subError2])) + }) + + it('can extend from another error with custom message and own custom message', () => { + const subError1 = new Error('subError1') + const subError2 = new Error('subError2') + const customMessage1 = 'customMessage1' + const customMessage2 = 'customMessage2' + const err = new AggregatedError([subError1], customMessage1).extend(subError2, customMessage2) + expect(err.message).toContain(subError1.message) + expect(err.message).toContain(subError2.message) + expect(err.message).toContain(customMessage1) + expect(err.message).toContain(customMessage2) + expect(err.stack).not.toBe('') + expect(err.errors).toEqual(new Set([subError1, subError2])) + }) + }) + + describe('from', () => { + it('does nothing with only oldErr', () => { + const subError = new Error('subError1') + const err = AggregatedError.from(subError) + expect(subError).toBe(err) + }) + + it('does nothing with only newErr', () => { + const subError = new Error('subError1') + const err = AggregatedError.from(undefined, subError) + expect(subError).toBe(err) + }) + + it('can rejig message', () => { + const subError = new Error('subError1') + const customMessage = 'customMessage' + const err = AggregatedError.from(undefined, subError, customMessage) + expect(err.message).toContain(customMessage) + expect(subError).toBe(err) + }) + + it('can extend from another Error with own custom message', () => { + const subError1 = new Error('subError1') + const subError2 = new Error('subError2') + const customMessage1 = 'customMessage2' + const err = AggregatedError.from(subError1, subError2, customMessage1) as AggregatedError + expect(err.message).toContain(subError1.message) + expect(err.message).toContain(subError2.message) + expect(err.message).toContain(customMessage1) + expect(err.stack).not.toBe('') + expect(err.errors).toEqual(new Set([subError1, subError2])) + expect(err).toBeInstanceOf(AggregatedError) + }) + + it('can extend from another AggregatedError with custom message and own custom message', () => { + const subError1 = new Error('subError1') + const subError2 = new Error('subError2') + const customMessage1 = 'customMessage1' + const customMessage2 = 'customMessage2' + const originalErr = new AggregatedError([subError1], customMessage1) + const err = AggregatedError.from(originalErr, subError2, customMessage2) as AggregatedError + expect(err.message).toContain(subError1.message) + expect(err.message).toContain(subError2.message) + expect(err.message).toContain(customMessage1) + expect(err.message).toContain(customMessage2) + expect(err.stack).not.toBe('') + expect(err.errors).toEqual(new Set([subError1, subError2])) + }) + }) + + describe('subclasses work', () => { + class MyError extends AggregatedError {} + it('does nothing with only oldErr', () => { + const subError = new Error('subError1') + const err = MyError.from(subError) + expect(subError).toBe(err) + }) + + it('can extend from another error with custom message and own custom message', () => { + const subError1 = new Error('subError1') + const subError2 = new Error('subError2') + const customMessage1 = 'customMessage1' + const customMessage2 = 'customMessage2' + const originalErr = new AggregatedError([subError1], customMessage1) + const err = MyError.from(originalErr, subError2, customMessage2) as AggregatedError + expect(err.message).toContain(subError1.message) + expect(err.message).toContain(subError2.message) + expect(err.message).toContain(customMessage1) + expect(err.message).toContain(customMessage2) + expect(err.stack).not.toBe('') + expect(err.errors).toEqual(new Set([subError1, subError2])) + expect(err).toBeInstanceOf(MyError) + }) + }) +}) diff --git a/tsconfig.json b/tsconfig.json index 16801cdce..1daeea6fc 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -22,6 +22,9 @@ "resolveJsonModule": true, "moduleResolution": "node" }, + "globals": { + "ts-jest": {} + }, "include": ["src/**/*", "contracts/*"], "exclude": ["node_modules", "dist"] } From e1907ad7cdf511c84640a153172d592257819a21 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 22 Feb 2021 14:59:17 -0500 Subject: [PATCH 486/517] Remove webpack node build, use tsc instead. --- package-lock.json | 6 ++++++ package.json | 9 ++++++--- src/StreamrClient.ts | 2 ++ test/benchmarks/publish.js | 2 +- test/benchmarks/subscribe.js | 2 +- tsconfig.node.json | 31 +++++++++++++++++++++++++++++++ webpack.config.js | 33 +-------------------------------- 7 files changed, 48 insertions(+), 37 deletions(-) create mode 100644 tsconfig.node.json diff --git a/package-lock.json b/package-lock.json index 805c5d3ba..89d98ea70 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2309,6 +2309,12 @@ "integrity": "sha512-8UT/J+xqCYfn3fKtOznAibsHpiuDshCb0fwgWxRazTT19Igp9ovoXMPhXyLD6m3CKQGTMHgqoxaFfMWaL40Rnw==", "dev": true }, + "@tsconfig/node14": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.0.tgz", + "integrity": "sha512-RKkL8eTdPv6t5EHgFKIVQgsDapugbuOptNd9OOunN/HAkzmmTnZELx1kNCK0rSdUYGmiFMM3rRQMAWiyp023LQ==", + "dev": true + }, "@types/asn1js": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/asn1js/-/asn1js-2.0.0.tgz", diff --git a/package.json b/package.json index e09c406d7..e349c4e6c 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "url": "git://github.com/streamr-dev/streamr-client.git" }, "types": "dist/types/src/StreamrClient.d.ts", - "main": "dist/streamr-client.nodejs.js", + "main": "dist/node/src/StreamrClient.js", "browser": "dist/streamr-client.web.min.js", "directories": { "example": "examples", @@ -15,8 +15,10 @@ }, "scripts": { "build": "rm -rf dist; NODE_ENV=production webpack --mode=production --progress && npm run build:types", - "build:types": "tsc --emitDeclarationOnly", - "check-types": "tsc --noEmit --project ./tsconfig.json", + "build-node": "tsc --incremental --project ./tsconfig.node.json", + "watch-node": "tsc --incremental --watch --project ./tsconfig.node.json", + "build:types": "tsc --incremental --emitDeclarationOnly", + "check-types": "tsc --noEmit --incremental --project ./tsconfig.json", "benchmarks": "node test/benchmarks/publish.js && node test/benchmarks/subscribe.js", "prebuild-benchmark": "npm run build -- --config-name=node-lib", "build-benchmark": "npm run benchmarks", @@ -55,6 +57,7 @@ "@babel/plugin-transform-runtime": "^7.12.17", "@babel/preset-env": "^7.12.17", "@babel/preset-typescript": "^7.12.17", + "@tsconfig/node14": "^1.0.0", "@types/debug": "^4.1.5", "@types/jest": "^26.0.20", "@types/lodash.uniqueid": "^4.0.6", diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index 6acf923fd..4188b71ce 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -400,3 +400,5 @@ class StreamrClient extends EventEmitter { } export default StreamrClient + +module.exports = StreamrClient diff --git a/test/benchmarks/publish.js b/test/benchmarks/publish.js index 89cb1641e..95b5c3906 100644 --- a/test/benchmarks/publish.js +++ b/test/benchmarks/publish.js @@ -3,7 +3,7 @@ const { format } = require('util') const { Benchmark } = require('benchmark') // eslint-disable-next-line import/no-unresolved -const StreamrClient = require('../../dist/streamr-client.nodejs.js') +const StreamrClient = require('../..') const config = require('../integration/config') /* eslint-disable no-console */ diff --git a/test/benchmarks/subscribe.js b/test/benchmarks/subscribe.js index 07683e80b..171380169 100644 --- a/test/benchmarks/subscribe.js +++ b/test/benchmarks/subscribe.js @@ -3,7 +3,7 @@ const { format } = require('util') const { Benchmark } = require('benchmark') // eslint-disable-next-line import/no-unresolved -const StreamrClient = require('../../dist/streamr-client.nodejs.js') +const StreamrClient = require('../..') const config = require('../integration/config') /* eslint-disable no-console */ diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 000000000..6776cd6c7 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,31 @@ +{ + "extends": "@tsconfig/node14/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist/node", + "lib": [ + "ES5", + "ES2015", + "ES2016", + "ES2017", + "ES2018", + "ES2019", + "ES2020", + "ESNext", + "DOM" + ], + "strict": true, + "moduleResolution": "node", + "resolveJsonModule": true + }, + "globals": { + "ts-jest": {} + }, + "include": [ + "src/**/*", + // quick-lru is esm, need to transpile :/ + "node_modules/quick-lru/index.js" + ] +} diff --git a/webpack.config.js b/webpack.config.js index 763ad6ee0..1837fd46c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -8,7 +8,6 @@ const path = require('path') const webpack = require('webpack') const TerserPlugin = require('terser-webpack-plugin') const { merge } = require('webpack-merge') -const nodeExternals = require('webpack-node-externals') const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer') const GitRevisionPlugin = require('git-revision-webpack-plugin') @@ -70,36 +69,6 @@ module.exports = (env, argv) => { ] } - const serverConfig = merge({}, commonConfig, { - name: 'node-lib', - target: 'node', - externals: [nodeExternals()], - output: { - libraryTarget: 'commonjs2', - filename: libraryName + '.nodejs.js', - }, - }) - - serverConfig.module.rules = [ - { - test: /(\.jsx|\.js|\.ts)$/, - exclude: /(node_modules|bower_components)/, - use: { - loader: 'babel-loader', - options: { - cacheDirectory: true, - configFile: path.resolve(__dirname, '.babel.node.config.js'), - babelrc: false, - } - } - }, - { - test: /(\.jsx|\.js|\.ts)$/, - loader: 'eslint-loader', - exclude: /(node_modules|streamr-client-protocol|dist)/, // excluding streamr-client-protocol makes build work when 'npm link'ed - }, - ] - const clientConfig = merge({}, commonConfig, { name: 'browser-lib', target: 'web', @@ -175,5 +144,5 @@ module.exports = (env, argv) => { }) } - return [serverConfig, clientConfig, clientMinifiedConfig].filter(Boolean) + return [clientConfig, clientMinifiedConfig].filter(Boolean) } From 8b879c7314d63e41b8b017d4a220d5e5d770f51e Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Mon, 22 Feb 2021 14:59:37 -0500 Subject: [PATCH 487/517] Re-enable no-undef eslint rule. --- .eslintrc.js | 1 - 1 file changed, 1 deletion(-) diff --git a/.eslintrc.js b/.eslintrc.js index 155803fe1..6f2a57e60 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -39,7 +39,6 @@ module.exports = { 'padded-blocks': 'off', 'no-use-before-define': 'off', 'import/order': 'off', - 'no-undef': 'off', 'no-shadow': 'off', '@typescript-eslint/no-shadow': 'error', 'no-unused-vars': 'off', From 4a7d14c421aaee41833db30b64319a6272e232ab Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 14:19:32 -0500 Subject: [PATCH 488/517] Linting & clean up DataUnion tests. --- .eslintrc.js | 10 +++++++++ src/rest/DataUnionEndpoints.ts | 4 ++-- test/integration/dataunion/member.test.ts | 2 +- test/integration/dataunion/signature.test.ts | 8 +++++-- test/integration/dataunion/withdraw.test.ts | 23 +++++++++++++++----- 5 files changed, 37 insertions(+), 10 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 6f2a57e60..7a82d59ec 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -6,6 +6,16 @@ module.exports = { extends: [ 'streamr-nodejs' ], + parserOptions: { + ecmaVersion: 2020, + ecmaFeatures: { + modules: true + } + }, + env: { + browser: true, + es6: true + }, rules: { 'max-len': ['warn', { code: 150 diff --git a/src/rest/DataUnionEndpoints.ts b/src/rest/DataUnionEndpoints.ts index b9d76a39c..e1e915ff4 100644 --- a/src/rest/DataUnionEndpoints.ts +++ b/src/rest/DataUnionEndpoints.ts @@ -535,9 +535,9 @@ function getMainnetContractReadOnly(contractAddress: EthereumAddress, client: St if (isAddress(contractAddress)) { const provider = client.ethereum.getMainnetProvider() return new Contract(contractAddress, dataUnionMainnetABI, provider) - } else { - throw new Error(`${contractAddress} was not a good Ethereum address`) } + throw new Error(`${contractAddress} was not a good Ethereum address`) + } function getMainnetContract(contractAddress: EthereumAddress, client: StreamrClient) { diff --git a/test/integration/dataunion/member.test.ts b/test/integration/dataunion/member.test.ts index 99d2e2df5..ca90b4f5c 100644 --- a/test/integration/dataunion/member.test.ts +++ b/test/integration/dataunion/member.test.ts @@ -25,7 +25,7 @@ const joinMember = async (memberWallet: Wallet, secret: string|undefined, dataUn } } as any) await memberClient.ensureConnected() - return await memberClient.getDataUnion(dataUnionAddress).join(secret) + return memberClient.getDataUnion(dataUnionAddress).join(secret) } describe('DataUnion member', () => { diff --git a/test/integration/dataunion/signature.test.ts b/test/integration/dataunion/signature.test.ts index 8981c29da..000befdea 100644 --- a/test/integration/dataunion/signature.test.ts +++ b/test/integration/dataunion/signature.test.ts @@ -52,8 +52,12 @@ it('DataUnion signature', async () => { const tokenSidechain = new Contract(config.clientOptions.tokenAddressSidechain, Token.abi, adminWalletSidechain) const signature = await memberClient.getDataUnion(dataUnion.getAddress()).signWithdrawAllTo(member2Wallet.address) - const signature2 = await memberClient.getDataUnion(dataUnion.getAddress()).signWithdrawAmountTo(member2Wallet.address, parseEther('1')) - const signature3 = await memberClient.getDataUnion(dataUnion.getAddress()).signWithdrawAmountTo(member2Wallet.address, 3000000000000000) // 0.003 tokens + const signature2 = await memberClient + .getDataUnion(dataUnion.getAddress()) + .signWithdrawAmountTo(member2Wallet.address, parseEther('1')) + const signature3 = await memberClient + .getDataUnion(dataUnion.getAddress()) + .signWithdrawAmountTo(member2Wallet.address, 3000000000000000) // 0.003 tokens const isValid = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, '0', signature) // '0' = all earnings const isValid2 = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, parseEther('1'), signature2) diff --git a/test/integration/dataunion/withdraw.test.ts b/test/integration/dataunion/withdraw.test.ts index 99740b07b..dd8ce036c 100644 --- a/test/integration/dataunion/withdraw.test.ts +++ b/test/integration/dataunion/withdraw.test.ts @@ -25,7 +25,12 @@ const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, const testWithdraw = async ( getBalanceBefore: (memberWallet: Wallet, adminTokenMainnet: Contract) => Promise, - withdraw: (dataUnionAddress: string, memberClient: StreamrClient, memberWallet: Wallet, adminClient: StreamrClient) => Promise, + withdraw: ( + dataUnionAddress: string, + memberClient: StreamrClient, + memberWallet: Wallet, + adminClient: StreamrClient + ) => Promise, getBalanceAfter: (memberWallet: Wallet, adminTokenMainnet: Contract) => Promise, requiresMainnetETH: boolean ) => { @@ -159,7 +164,9 @@ describe('DataUnion withdraw', () => { it('by member itself', () => { const getBalanceBefore = async (memberWallet: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(memberWallet.address) - const withdraw = async (dataUnionAddress: string, memberClient: StreamrClient) => memberClient.getDataUnion(dataUnionAddress).withdrawAll() + const withdraw = async (dataUnionAddress: string, memberClient: StreamrClient) => ( + memberClient.getDataUnion(dataUnionAddress).withdrawAll() + ) const getBalanceAfter = async (memberWallet: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(memberWallet.address) return testWithdraw(getBalanceBefore, withdraw, getBalanceAfter, true) }, 300000) @@ -167,7 +174,9 @@ describe('DataUnion withdraw', () => { it('from member to any address', () => { const outsiderWallet = new Wallet(`0x100000000000000000000000000000000000000012300000002${Date.now()}`, providerSidechain) const getBalanceBefore = (_: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(outsiderWallet.address) - const withdraw = (dataUnionAddress: string, memberClient: StreamrClient) => memberClient.getDataUnion(dataUnionAddress).withdrawAllTo(outsiderWallet.address) + const withdraw = (dataUnionAddress: string, memberClient: StreamrClient) => ( + memberClient.getDataUnion(dataUnionAddress).withdrawAllTo(outsiderWallet.address) + ) const getBalanceAfter = (_: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(outsiderWallet.address) return testWithdraw(getBalanceBefore, withdraw, getBalanceAfter, true) }, 300000) @@ -177,7 +186,9 @@ describe('DataUnion withdraw', () => { describe('Admin', () => { it('non-signed', async () => { const getBalanceBefore = (memberWallet: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(memberWallet.address) - const withdraw = (dataUnionAddress: string, _: StreamrClient, memberWallet: Wallet, adminClient: StreamrClient) => adminClient.getDataUnion(dataUnionAddress).withdrawAllToMember(memberWallet.address) + const withdraw = (dataUnionAddress: string, _: StreamrClient, memberWallet: Wallet, adminClient: StreamrClient) => ( + adminClient.getDataUnion(dataUnionAddress).withdrawAllToMember(memberWallet.address) + ) const getBalanceAfter = (memberWallet: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(memberWallet.address) return testWithdraw(getBalanceBefore, withdraw, getBalanceAfter, false) }, 300000) @@ -187,7 +198,9 @@ describe('DataUnion withdraw', () => { const getBalanceBefore = (_: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(member2Wallet.address) const withdraw = async (dataUnionAddress: string, memberClient: StreamrClient, memberWallet: Wallet, adminClient: StreamrClient) => { const signature = await memberClient.getDataUnion(dataUnionAddress).signWithdrawAllTo(member2Wallet.address) - const withdrawTr = await adminClient.getDataUnion(dataUnionAddress).withdrawAllToSigned(memberWallet.address, member2Wallet.address, signature) + const withdrawTr = await adminClient + .getDataUnion(dataUnionAddress) + .withdrawAllToSigned(memberWallet.address, member2Wallet.address, signature) return withdrawTr } const getBalanceAfter = (_: Wallet, adminTokenMainnet: Contract) => adminTokenMainnet.balanceOf(member2Wallet.address) From ff9262a724ac62ce4ac2b98710ba3d4317738a0a Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 14:37:38 -0500 Subject: [PATCH 489/517] Remove check-types script in favour of test-types. --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index e349c4e6c..b60ef536f 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,6 @@ "build-node": "tsc --incremental --project ./tsconfig.node.json", "watch-node": "tsc --incremental --watch --project ./tsconfig.node.json", "build:types": "tsc --incremental --emitDeclarationOnly", - "check-types": "tsc --noEmit --incremental --project ./tsconfig.json", "benchmarks": "node test/benchmarks/publish.js && node test/benchmarks/subscribe.js", "prebuild-benchmark": "npm run build -- --config-name=node-lib", "build-benchmark": "npm run benchmarks", @@ -30,7 +29,7 @@ "eslint": "eslint --cache-location=node_modules/.cache/.eslintcache/ '*/**/*.{js,ts}'", "test": "jest --detectOpenHandles", "test-unit": "jest test/unit --detectOpenHandles", - "test-types": "tsc --noEmit --project ./tsconfig.test.json", + "test-types": "tsc --noEmit --incremental --project ./tsconfig.test.json", "coverage": "jest --coverage", "test-integration": "jest --forceExit test/integration", "test-integration-no-resend": "jest --forceExit --testTimeout=10000 --testPathIgnorePatterns='resend|Resend' --testNamePattern='^((?!(resend|Resend|resent|Resent|gap|Gap)).)*$' test/integration/*.test.js", From 08f864a8a8f03e1d885c5ebe63c19e4d7a1b47b4 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 14:38:29 -0500 Subject: [PATCH 490/517] Error if trying to get sidechainProvider and none configured. --- src/Ethereum.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/Ethereum.js b/src/Ethereum.js index ad0b91725..dd39f568d 100644 --- a/src/Ethereum.js +++ b/src/Ethereum.js @@ -62,6 +62,7 @@ export default class StreamrEthereum { // _getAddress is assigned in constructor throw new Error('StreamrClient is not authenticated with private key') } + return this._getAddress() } @@ -70,6 +71,7 @@ export default class StreamrEthereum { // _getSigner is assigned in constructor throw new Error("StreamrClient not authenticated! Can't send transactions or sign messages.") } + return this._getSigner() } @@ -78,22 +80,25 @@ export default class StreamrEthereum { // _getSidechainSigner is assigned in constructor throw new Error("StreamrClient not authenticated! Can't send transactions or sign messages.") } + return this._getSidechainSigner() } /** @returns Ethers.js Provider, a connection to the Ethereum network (mainnet) */ getMainnetProvider() { - if (this.client.options.mainnet) { - return new JsonRpcProvider(this.client.options.mainnet) + if (!this.client.options.mainnet) { + return getDefaultProvider() } - return getDefaultProvider() + + return new JsonRpcProvider(this.client.options.mainnet) } /** @returns Ethers.js Provider, a connection to the Streamr EVM sidechain */ getSidechainProvider() { - if (this.client.options.sidechain) { - return new JsonRpcProvider(this.client.options.sidechain) + if (!this.client.options.sidechain) { + throw new Error('StreamrClient has no sidechain configuration.') } - return null + + return new JsonRpcProvider(this.client.options.sidechain) } } From a8bab9ecb1acad3c3fda120c66836c43db6f9389 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 14:39:21 -0500 Subject: [PATCH 491/517] Fix unused or simple to fix @ts-expect-error directives. --- src/rest/DataUnionEndpoints.ts | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/src/rest/DataUnionEndpoints.ts b/src/rest/DataUnionEndpoints.ts index e1e915ff4..5c1113161 100644 --- a/src/rest/DataUnionEndpoints.ts +++ b/src/rest/DataUnionEndpoints.ts @@ -305,10 +305,8 @@ async function getSidechainAmb(client: StreamrClient) { outputs: [{ type: 'address' }], stateMutability: 'view', type: 'function' - // @ts-expect-error }], sidechainProvider) const sidechainAmbAddress = await factorySidechain.amb() - // @ts-expect-error return new Contract(sidechainAmbAddress, sidechainAmbABI, sidechainProvider) } cachedSidechainAmb = getAmbPromise() @@ -366,8 +364,7 @@ async function transportSignatures(client: StreamrClient, messageHash: string) { let gasLimit try { // magic number suggested by https://github.com/poanetwork/tokenbridge/blob/master/oracle/src/utils/constants.js - // @ts-expect-error - gasLimit = await mainnetAmb.estimateGas.executeSignatures(message, packedSignatures) + 200000 + gasLimit = BigNumber.from(await mainnetAmb.estimateGas.executeSignatures(message, packedSignatures)).add(200000) log(`Calculated gas limit: ${gasLimit.toString()}`) } catch (e) { // Failure modes from https://github.com/poanetwork/tokenbridge/blob/master/oracle/src/events/processAMBCollectedSignatures/estimateGas.js @@ -416,9 +413,7 @@ async function transportSignatures(client: StreamrClient, messageHash: string) { } const signer = client.ethereum.getSigner() - // @ts-expect-error log(`Sending message from signer=${await signer.getAddress()}`) - // @ts-expect-error const txAMB = await mainnetAmb.connect(signer).executeSignatures(message, packedSignatures) const trAMB = await txAMB.wait() return trAMB @@ -543,7 +538,6 @@ function getMainnetContractReadOnly(contractAddress: EthereumAddress, client: St function getMainnetContract(contractAddress: EthereumAddress, client: StreamrClient) { const du = getMainnetContractReadOnly(contractAddress, client) const signer = client.ethereum.getSigner() - // @ts-expect-error return du.connect(signer) } @@ -551,7 +545,6 @@ async function getSidechainContract(contractAddress: EthereumAddress, client: St const signer = await client.ethereum.getSidechainSigner() const duMainnet = getMainnetContractReadOnly(contractAddress, client) const duSidechainAddress = getDataUnionSidechainAddress(client, duMainnet.address) - // @ts-expect-error const duSidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, signer) return duSidechain } @@ -560,7 +553,6 @@ async function getSidechainContractReadOnly(contractAddress: EthereumAddress, cl const provider = client.ethereum.getSidechainProvider() const duMainnet = getMainnetContractReadOnly(contractAddress, client) const duSidechainAddress = getDataUnionSidechainAddress(client, duMainnet.address) - // @ts-expect-error const duSidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, provider) return duSidechain } @@ -634,8 +626,7 @@ export class DataUnionEndpoints { } const deployerAddress = this.client.getAddress() - // @ts-expect-error - const duMainnetAddress = await fetchDataUnionMainnetAddress(this.client, duName, deployerAddress, options) + const duMainnetAddress = await fetchDataUnionMainnetAddress(this.client, duName, deployerAddress) const duSidechainAddress = await fetchDataUnionSidechainAddress(this.client, duMainnetAddress) if (await mainnetProvider.getCode(duMainnetAddress) !== '0x') { @@ -651,7 +642,6 @@ export class DataUnionEndpoints { } // function deployNewDataUnion(address owner, uint256 adminFeeFraction, address[] agents, string duName) - // @ts-expect-error const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, mainnetWallet) const ethersOptions: any = {} if (gasPrice) { @@ -668,13 +658,11 @@ export class DataUnionEndpoints { log(`Data Union "${duName}" (mainnet: ${duMainnetAddress}, sidechain: ${duSidechainAddress}) deployed to mainnet, waiting for side-chain...`) await until( - // @ts-expect-error async () => await sidechainProvider.getCode(duSidechainAddress) !== '0x', sidechainRetryTimeoutMs, sidechainPollingIntervalMs ) - // @ts-expect-error const dataUnion = new Contract(duMainnetAddress, dataUnionMainnetABI, mainnetWallet) // @ts-expect-error dataUnion.deployTxReceipt = tr @@ -1015,7 +1003,6 @@ export class DataUnionEndpoints { */ async getWithdrawAllTx(contractAddress: EthereumAddress): Promise { const signer = await this.client.ethereum.getSidechainSigner() - // @ts-expect-error const address = await signer.getAddress() const duSidechain = await getSidechainContract(contractAddress, this.client) @@ -1057,7 +1044,6 @@ export class DataUnionEndpoints { */ async getWithdrawAllToTx(recipientAddress: EthereumAddress, contractAddress: EthereumAddress): Promise { const signer = await this.client.ethereum.getSidechainSigner() - // @ts-expect-error const address = await signer.getAddress() const duSidechain = await getSidechainContract(contractAddress, this.client) const withdrawable = await duSidechain.getWithdrawableEarnings(address) @@ -1099,7 +1085,6 @@ export class DataUnionEndpoints { ): Promise { const to = getAddress(recipientAddress) // throws if bad address const signer = this.client.ethereum.getSigner() // it shouldn't matter if it's mainnet or sidechain signer since key should be the same - // @ts-expect-error const address = await signer.getAddress() const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) const memberData = await duSidechain.memberData(address) @@ -1107,7 +1092,6 @@ export class DataUnionEndpoints { const withdrawn = memberData[3] // @ts-expect-error const message = to + hexZeroPad(amountTokenWei, 32).slice(2) + duSidechain.address.slice(2) + hexZeroPad(withdrawn, 32).slice(2) - // @ts-expect-error const signature = await signer.signMessage(arrayify(message)) return signature } From 41a03c572d3c5e6a8952508bc2f77ac143639ff7 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 14:47:29 -0500 Subject: [PATCH 492/517] Remove config.sidechainAmbAddress, add factorySidechainAddress. --- src/Config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Config.ts b/src/Config.ts index 561a96733..1716e547b 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -41,7 +41,7 @@ export type StreamrClientOptions = { minimumWithdrawTokenWei?: BigNumber|number|string, sidechainTokenAddress?: string factoryMainnetAddress?: string - sidechainAmbAddress?: string + factorySidechainAddress?: string payForSignatureTransport?: boolean cache?: { maxSize?: number, @@ -94,7 +94,7 @@ export default function ClientConfig(opts: Partial = {}) { minimumWithdrawTokenWei: '1000000', // Threshold value set in AMB configs, smallest token amount to pass over the bridge sidechainTokenAddress: undefined, // TODO // sidechain token factoryMainnetAddress: undefined, // TODO // Data Union factory that creates a new Data Union - sidechainAmbAddress: undefined, // Arbitrary Message-passing Bridge (AMB), see https://github.com/poanetwork/tokenbridge + factorySidechainAddress: undefined, payForSignatureTransport: true, // someone must pay for transporting the withdraw tx to mainnet, either us or bridge operator cache: { maxSize: 10000, From b048e7ad26892408b84a8699c78931dd180c8502 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 14:48:48 -0500 Subject: [PATCH 493/517] Throw if trying to use missing factorySidechainAddress, factoryMainnetAddress or tokenAddress config. --- src/rest/DataUnionEndpoints.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/rest/DataUnionEndpoints.ts b/src/rest/DataUnionEndpoints.ts index 5c1113161..e78c1a55a 100644 --- a/src/rest/DataUnionEndpoints.ts +++ b/src/rest/DataUnionEndpoints.ts @@ -498,6 +498,9 @@ async function fetchDataUnionMainnetAddress( function getDataUnionMainnetAddress(client: StreamrClient, dataUnionName: string, deployerAddress: EthereumAddress) { const { factoryMainnetAddress } = client.options + if (!factoryMainnetAddress) { + throw new Error('StreamrClient has no factoryMainnetAddress configuration.') + } // NOTE! this must be updated when DU sidechain smartcontract changes: keccak256(CloneLib.cloneBytecode(data_union_mainnet_template)); const codeHash = '0x50a78bac973bdccfc8415d7d9cfd62898b8f7cf6e9b3a15e7d75c0cb820529eb' const salt = keccak256(defaultAbiCoder.encode(['string', 'address'], [dataUnionName, deployerAddress])) @@ -521,6 +524,9 @@ async function fetchDataUnionSidechainAddress(client: StreamrClient, duMainnetAd function getDataUnionSidechainAddress(client: StreamrClient, mainnetAddress: EthereumAddress) { const { factorySidechainAddress } = client.options + if (!factorySidechainAddress) { + throw new Error('StreamrClient has no factorySidechainAddress configuration.') + } // NOTE! this must be updated when DU sidechain smartcontract changes: keccak256(CloneLib.cloneBytecode(data_union_sidechain_template)) const codeHash = '0x040cf686e25c97f74a23a4bf01c29dd77e260c4b694f5611017ce9713f58de83' return getCreate2Address(factorySidechainAddress, hexZeroPad(mainnetAddress, 32), codeHash) @@ -941,9 +947,14 @@ export class DataUnionEndpoints { * Get token balance in "wei" (10^-18 parts) for given address */ async getTokenBalance(address: string): Promise { - const a = getAddress(address) + const { tokenAddress } = this.client.options + if (!tokenAddress) { + throw new Error('StreamrClient has no tokenAddress configuration.') + } + const addr = getAddress(address) const provider = this.client.ethereum.getMainnetProvider() - const token = new Contract(this.client.options.tokenAddress, [{ + + const token = new Contract(tokenAddress, [{ name: 'balanceOf', inputs: [{ type: 'address' }], outputs: [{ type: 'uint256' }], @@ -952,7 +963,7 @@ export class DataUnionEndpoints { stateMutability: 'view', type: 'function' }], provider) - return token.balanceOf(a) + return token.balanceOf(addr) } /** From 54b1d46f94a8d1381407c62e8310d0f23527d422 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 14:48:59 -0500 Subject: [PATCH 494/517] TS Linting. --- test/unit/AggregatedError.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/AggregatedError.test.ts b/test/unit/AggregatedError.test.ts index 3eeb9776b..5ca4ac5fc 100644 --- a/test/unit/AggregatedError.test.ts +++ b/test/unit/AggregatedError.test.ts @@ -91,7 +91,7 @@ describe('AggregatedError', () => { const subError = new Error('subError1') const customMessage = 'customMessage' const err = AggregatedError.from(undefined, subError, customMessage) - expect(err.message).toContain(customMessage) + expect(err && err.message).toContain(customMessage) expect(subError).toBe(err) }) From 108ed5f9591e882d9bc4e746ff44adc429b9b88e Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 14:53:14 -0500 Subject: [PATCH 495/517] Base test tsconfig on node tsconfig. --- tsconfig.node.json | 1 + tsconfig.test.json | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/tsconfig.node.json b/tsconfig.node.json index 6776cd6c7..0b2d144c9 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -25,6 +25,7 @@ }, "include": [ "src/**/*", + "contracts/**/*", // quick-lru is esm, need to transpile :/ "node_modules/quick-lru/index.js" ] diff --git a/tsconfig.test.json b/tsconfig.test.json index bd467276a..15f2d7de6 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -1,5 +1,5 @@ { - "extends": "./tsconfig.json", - "include": ["src/**/*", "contracts/**/*", "test/**/*"], + "extends": "./tsconfig.node.json", + "include": ["test/**/*"], "exclude": ["node_modules", "dist", "test/legacy/*"] } From 4cd05c4337ba28091a72c27eca8077f27abc2ce2 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 14:57:47 -0500 Subject: [PATCH 496/517] Build node lib in npm run build. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b60ef536f..b035eb7a9 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "test": "test" }, "scripts": { - "build": "rm -rf dist; NODE_ENV=production webpack --mode=production --progress && npm run build:types", + "build": "rm -rf dist; NODE_ENV=production webpack --mode=production --progress && npm run build-node && npm run build:types", "build-node": "tsc --incremental --project ./tsconfig.node.json", "watch-node": "tsc --incremental --watch --project ./tsconfig.node.json", "build:types": "tsc --incremental --emitDeclarationOnly", From 6d9e9cb7de45494ebb1b7a5aaa5345cfc4550eb6 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 14:58:18 -0500 Subject: [PATCH 497/517] Remove no longer needed webpack export default workaround. --- webpack.config.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/webpack.config.js b/webpack.config.js index 1837fd46c..dd8dd3c43 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -75,19 +75,6 @@ module.exports = (env, argv) => { output: { libraryTarget: 'umd2', filename: libraryName + '.web.js', - // NOTE: - // exporting the class directly - // `export default class StreamrClient {}` - // becomes: - // `window.StreamrClient === StreamrClient` - // which is correct, but if we define the class and export separately, - // which is required if we do interface StreamrClient extends …: - // `class StreamrClient {}; export default StreamrClient;` - // becomes: - // `window.StreamrClient = { default: StreamrClient, … }` - // which is wrong for browser builds. - // see: https://github.com/webpack/webpack/issues/706#issuecomment-438007763 - libraryExport: 'default', // This fixes the above. library: 'StreamrClient', }, resolve: { From aaed908a20b8551070e8bf2835edd74d928d85f5 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 16:09:18 -0500 Subject: [PATCH 498/517] Release v5.0.0-beta.7 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 47bd4c852..48fe487a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.6", + "version": "5.0.0-beta.7", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 085914a4b..f0bdfdf67 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.6", + "version": "5.0.0-beta.7", "description": "JavaScript client library for Streamr", "repository": { "type": "git", From 6ce6ffec0516a34735c2e196ee4cf342b838c26d Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 20:36:49 -0500 Subject: [PATCH 499/517] Convert publish/Encrypt.js -> publish/Encrypt.ts. --- src/publish/{Encrypt.js => Encrypt.ts} | 16 ++++++++++------ src/stream/index.ts | 2 ++ 2 files changed, 12 insertions(+), 6 deletions(-) rename src/publish/{Encrypt.js => Encrypt.ts} (64%) diff --git a/src/publish/Encrypt.js b/src/publish/Encrypt.ts similarity index 64% rename from src/publish/Encrypt.js rename to src/publish/Encrypt.ts index f26ee26ec..0dbde22d4 100644 --- a/src/publish/Encrypt.js +++ b/src/publish/Encrypt.ts @@ -1,17 +1,21 @@ import { MessageLayer } from 'streamr-client-protocol' import EncryptionUtil from '../stream/Encryption' +import type Stream from '../stream' +import type StreamrClient from '../StreamrClient' import { PublisherKeyExhange } from '../stream/KeyExchange' const { StreamMessage } = MessageLayer -export default function Encrypt(client) { +type PublisherKeyExhangeAPI = ReturnType + +export default function Encrypt(client: StreamrClient) { const publisherKeyExchange = PublisherKeyExhange(client, { groupKeys: { ...client.options.groupKeys, } }) - async function encrypt(streamMessage, stream) { + async function encrypt(streamMessage: MessageLayer.StreamMessage, stream: Stream) { if ( !publisherKeyExchange.hasAnyGroupKey(stream.id) && !stream.requireEncryptedData @@ -28,14 +32,14 @@ export default function Encrypt(client) { } return Object.assign(encrypt, { - setNextGroupKey(...args) { + setNextGroupKey(...args: Parameters) { return publisherKeyExchange.setNextGroupKey(...args) }, - rotateGroupKey(...args) { + rotateGroupKey(...args: Parameters) { return publisherKeyExchange.rotateGroupKey(...args) }, - stop(...args) { - return publisherKeyExchange.stop(...args) + stop() { + return publisherKeyExchange.stop() } }) } diff --git a/src/stream/index.ts b/src/stream/index.ts index e8f03fe1f..aad96d989 100644 --- a/src/stream/index.ts +++ b/src/stream/index.ts @@ -50,6 +50,8 @@ export default class Stream { fields: Field[]; } = { fields: [] } _client: StreamrClient + requireEncryptedData?: boolean + requireSignedData?: boolean constructor(client: StreamrClient, props: StreamProperties) { this._client = client From f1a6d3bd32275dfbee836c475f154c7c038d0933 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 21:00:24 -0500 Subject: [PATCH 500/517] Convert publish/Signer.js -> publish/Signer.ts. --- src/Config.ts | 4 +++- src/publish/{Signer.js => Signer.ts} | 31 ++++++++++++++++++++++------ 2 files changed, 28 insertions(+), 7 deletions(-) rename src/publish/{Signer.js => Signer.ts} (73%) diff --git a/src/Config.ts b/src/Config.ts index 1716e547b..576b3dcce 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -7,12 +7,14 @@ import { O } from 'ts-toolbelt' import { getVersionString, counterId } from './utils' import { Todo } from './types' +export type EthereumConfig = ExternalProvider|JsonRpcFetchFunc + export type StreamrClientOptions = { id?: string debug?: Debug.Debugger, auth?: { privateKey?: string - ethereum?: ExternalProvider|JsonRpcFetchFunc, + ethereum?: EthereumConfig apiKey?: string username?: string password?: string diff --git a/src/publish/Signer.js b/src/publish/Signer.ts similarity index 73% rename from src/publish/Signer.js rename to src/publish/Signer.ts index 1b7ece7cd..44e5bb875 100644 --- a/src/publish/Signer.js +++ b/src/publish/Signer.ts @@ -2,17 +2,32 @@ import { MessageLayer, Utils } from 'streamr-client-protocol' import { Web3Provider } from '@ethersproject/providers' import { pLimitFn, sleep } from '../utils' +import type { EthereumConfig } from '../Config' const { StreamMessage } = MessageLayer const { SigningUtil } = Utils const { SIGNATURE_TYPES } = StreamMessage -function getSigningFunction({ privateKey, ethereum } = {}) { +type AuthOption = { + ethereum: undefined + privateKey: string | Uint8Array +} | { + privateKey: undefined + ethereum: EthereumConfig +} | { + ethereum: undefined + privateKey: undefined +} + +function getSigningFunction({ + privateKey, + ethereum, +}: AuthOption) { if (privateKey) { const key = (typeof privateKey === 'string' && privateKey.startsWith('0x')) ? privateKey.slice(2) // strip leading 0x : privateKey - return async (d) => SigningUtil.sign(d, key) + return async (d: string) => SigningUtil.sign(d, key.toString()) } if (ethereum) { @@ -30,15 +45,16 @@ function getSigningFunction({ privateKey, ethereum } = {}) { throw new Error('Need either "privateKey" or "ethereum".') } -export default function Signer(options = {}, publishWithSignature = 'auto') { +export default function Signer(options: AuthOption, publishWithSignature = 'auto') { const { privateKey, ethereum } = options + const noSignStreamMessage = (streamMessage: MessageLayer.StreamMessage) => streamMessage if (publishWithSignature === 'never') { - return (v) => v + return noSignStreamMessage } if (publishWithSignature === 'auto' && !privateKey && !ethereum) { - return (v) => v + return noSignStreamMessage } if (publishWithSignature !== 'auto' && publishWithSignature !== 'always') { @@ -47,7 +63,10 @@ export default function Signer(options = {}, publishWithSignature = 'auto') { const sign = getSigningFunction(options) - async function signStreamMessage(streamMessage, signatureType = SIGNATURE_TYPES.ETH) { + async function signStreamMessage( + streamMessage: MessageLayer.StreamMessage, + signatureType: MessageLayer.StreamMessage['signatureType'] = SIGNATURE_TYPES.ETH + ) { if (!streamMessage) { throw new Error('streamMessage required as part of the data to sign.') } From 0071715c0de20c222e8e4685cf2c648fc877ab53 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 21:01:36 -0500 Subject: [PATCH 501/517] Convert stream/StorageNode.js -> stream/StorageNode.ts. --- src/stream/{StorageNode.js => StorageNode.ts} | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) rename src/stream/{StorageNode.js => StorageNode.ts} (69%) diff --git a/src/stream/StorageNode.js b/src/stream/StorageNode.ts similarity index 69% rename from src/stream/StorageNode.js rename to src/stream/StorageNode.ts index 604787303..83d223561 100644 --- a/src/stream/StorageNode.js +++ b/src/stream/StorageNode.ts @@ -1,5 +1,6 @@ export default class StorageNode { - constructor(address) { + _address: string + constructor(address: string) { this._address = address } From 219a596e6faa084eaea5ec921b081cfc4addf42b Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Tue, 23 Feb 2021 16:53:57 -0500 Subject: [PATCH 502/517] Tell jest to ignore dist. Prevents jest-haste-map complaining. --- jest.config.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index ce4817842..d5ece98e6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -76,7 +76,9 @@ module.exports = { // moduleNameMapper: {}, // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader - // modulePathIgnorePatterns: [], + modulePathIgnorePatterns: [ + '/dist', + ], // Activates notifications for test results // notify: false, From 17c996c7644a2a0d2f29a219144c41e65d51a4b9 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Mon, 1 Mar 2021 15:46:26 +0200 Subject: [PATCH 503/517] DataUnions: encapsulate contract handling, remove obsolete wrappers, improve validation, memberStats format (#206) Split DataUnionEndpoints.ts: move contract handling to Contracts.ts, ABI definitions to abi.ts and inline public methods to DataUnion.ts Smaller changes: - Remove obsolete caching from address fetching - Improve address validation (and add tests) - Split tests, improve test cleanup - Bug fix for payForSignatureTransport option in withdraws: method option overrides client option - Format of getMemberStats() data modified (enum values changed, number values string->BigNumber) - Type annotations for StreamrClientOptions: many of the fields are required, as we apply a default value -> users give options as Partial - Id and debug are client properties, not options --- package-lock.json | 9 - package.json | 2 +- src/Config.ts | 81 +- src/Connection.js | 18 +- src/StreamrClient.ts | 58 +- src/dataunion/Contracts.ts | 329 +++++ src/dataunion/DataUnion.ts | 517 +++++++- src/dataunion/abi.ts | 223 ++++ src/rest/DataUnionEndpoints.ts | 1110 ----------------- src/rest/authFetch.js | 2 +- src/types.ts | 2 + .../dataunion/DataUnionEndpoints.test.ts | 219 ---- test/integration/dataunion/adminFee.test.ts | 80 ++ test/integration/dataunion/calculate.test.ts | 52 +- test/integration/dataunion/deploy.test.ts | 13 +- test/integration/dataunion/member.test.ts | 20 +- test/integration/dataunion/signature.test.ts | 100 +- test/integration/dataunion/stats.test.ts | 98 ++ test/integration/dataunion/withdraw.test.ts | 40 +- test/{utils.js => utils.ts} | 63 +- 20 files changed, 1450 insertions(+), 1586 deletions(-) create mode 100644 src/dataunion/Contracts.ts create mode 100644 src/dataunion/abi.ts delete mode 100644 src/rest/DataUnionEndpoints.ts delete mode 100644 test/integration/dataunion/DataUnionEndpoints.test.ts create mode 100644 test/integration/dataunion/adminFee.test.ts create mode 100644 test/integration/dataunion/stats.test.ts rename test/{utils.js => utils.ts} (75%) diff --git a/package-lock.json b/package-lock.json index d6c93ab16..5a1a2dda0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3103,15 +3103,6 @@ "dev": true, "optional": true }, - "async-mutex": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.3.1.tgz", - "integrity": "sha512-vRfQwcqBnJTLzVQo72Sf7KIUbcSUP5hNchx6udI1U6LuPQpfePgdjJzlCe76yFZ8pxlLjn9lwcl/Ya0TSOv0Tw==", - "dev": true, - "requires": { - "tslib": "^2.1.0" - } - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", diff --git a/package.json b/package.json index bb17634fb..5ffed06f4 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,6 @@ "@types/uuid": "^8.3.0", "@typescript-eslint/eslint-plugin": "^4.15.1", "@typescript-eslint/parser": "^4.15.1", - "async-mutex": "^0.3.0", "babel-loader": "^8.2.2", "babel-plugin-add-module-exports": "^1.0.4", "babel-plugin-transform-class-properties": "^6.24.1", @@ -114,6 +113,7 @@ "@ethersproject/sha2": "^5.0.8", "@ethersproject/transactions": "^5.0.10", "@ethersproject/wallet": "^5.0.11", + "@ethersproject/web": "^5.0.13", "debug": "^4.3.2", "eventemitter3": "^4.0.7", "lodash.uniqueid": "^4.0.1", diff --git a/src/Config.ts b/src/Config.ts index 576b3dcce..54d903dc9 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -1,65 +1,55 @@ import qs from 'qs' -import Debug from 'debug' import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import { ExternalProvider, JsonRpcFetchFunc } from '@ethersproject/providers' import { BigNumber } from '@ethersproject/bignumber' -import { O } from 'ts-toolbelt' -import { getVersionString, counterId } from './utils' +import { getVersionString } from './utils' +import { ConnectionInfo } from '@ethersproject/web' import { Todo } from './types' export type EthereumConfig = ExternalProvider|JsonRpcFetchFunc export type StreamrClientOptions = { - id?: string - debug?: Debug.Debugger, - auth?: { + auth: { privateKey?: string ethereum?: EthereumConfig apiKey?: string username?: string password?: string } - url?: string - restUrl?: string - streamrNodeAddress?: string - autoConnect?: boolean - autoDisconnect?: boolean - orderMessages?: boolean, - retryResendAfter?: number, - gapFillTimeout?: number, - maxGapRequests?: number, - maxPublishQueueSize?: number, - publishWithSignature?: Todo, - verifySignatures?: Todo, - publisherStoreKeyHistory?: boolean, - groupKeys?: Todo - keyExchange?: Todo - mainnet?: Todo - sidechain?: { - url?: string - }, + url: string + restUrl: string + streamrNodeAddress: string + autoConnect: boolean + autoDisconnect: boolean + orderMessages: boolean + retryResendAfter: number + gapFillTimeout: number + maxGapRequests: number + maxPublishQueueSize: number + publishWithSignature: Todo + verifySignatures: Todo + publisherStoreKeyHistory: boolean + groupKeys: Todo + keyExchange: Todo + mainnet?: ConnectionInfo|string + sidechain?: ConnectionInfo|string dataUnion?: string - tokenAddress?: string, - minimumWithdrawTokenWei?: BigNumber|number|string, - sidechainTokenAddress?: string - factoryMainnetAddress?: string - factorySidechainAddress?: string - payForSignatureTransport?: boolean - cache?: { - maxSize?: number, - maxAge?: number + tokenAddress: string, + minimumWithdrawTokenWei?: BigNumber|number|string + factoryMainnetAddress: string + factorySidechainAddress: string + payForSignatureTransport: boolean + cache: { + maxSize: number, + maxAge: number } } -export type StreamrClientConfig = O.Compulsory const { ControlMessage } = ControlLayer const { StreamMessage } = MessageLayer export default function ClientConfig(opts: Partial = {}) { - const { id = counterId('StreamrClient') } = opts - - const defaults = { - debug: Debug(id), + const defaults: StreamrClientOptions = { // Authentication: identity used by this StreamrClient instance auth: {}, // can contain member privateKey or (window.)ethereum @@ -87,16 +77,11 @@ export default function ClientConfig(opts: Partial = {}) { // Ethereum and Data Union related options // For ethers.js provider params, see https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#provider mainnet: undefined, // Default to ethers.js default provider settings - sidechain: { - url: undefined, // TODO: add our default public service sidechain node, also find good PoA params below - // timeout: - // pollingInterval: - }, + sidechain: undefined, // TODO: add our default public service sidechain node, also find good PoA params below tokenAddress: '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', minimumWithdrawTokenWei: '1000000', // Threshold value set in AMB configs, smallest token amount to pass over the bridge - sidechainTokenAddress: undefined, // TODO // sidechain token - factoryMainnetAddress: undefined, // TODO // Data Union factory that creates a new Data Union - factorySidechainAddress: undefined, + factoryMainnetAddress: 'TODO', // TODO // Data Union factory that creates a new Data Union + factorySidechainAddress: 'TODO', payForSignatureTransport: true, // someone must pay for transporting the withdraw tx to mainnet, either us or bridge operator cache: { maxSize: 10000, @@ -104,7 +89,7 @@ export default function ClientConfig(opts: Partial = {}) { } } - const options: StreamrClientConfig = { + const options: StreamrClientOptions = { ...defaults, ...opts, cache: { diff --git a/src/Connection.js b/src/Connection.js index d804ec041..ddff7ecde 100644 --- a/src/Connection.js +++ b/src/Connection.js @@ -63,12 +63,8 @@ async function OpenWebSocket(url, opts, ...args) { }) // attach debug - if (opts && opts.debug) { - socket.debug = opts.debug.extend(socket.id) - socket.debug.color = opts.debug.color // use existing colour - } else { - socket.debug = Debug('StreamrClient::ws').extend(socket.id) - } + socket.debug = opts.debug.extend(socket.id) + socket.debug.color = opts.debug.color // use existing colour } catch (err) { reject(err) } @@ -292,15 +288,9 @@ export default class Connection extends EventEmitter { })) } - constructor(options = {}) { + constructor(options = {}, client) { super() - const id = counterId(this.constructor.name) - /* istanbul ignore next */ - if (options.debug) { - this._debug = options.debug.extend(id) - } else { - this._debug = Debug(`StreamrClient::${id}`) - } + this._debug = client.debug.extend(counterId(this.constructor.name)) this.options = options this.options.autoConnect = !!this.options.autoConnect diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index 4188b71ce..3f0202ac4 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -4,7 +4,7 @@ import Debug from 'debug' import { counterId, uuid, CacheAsyncFn } from './utils' import { validateOptions } from './stream/utils' -import Config, { StreamrClientOptions, StreamrClientConfig } from './Config' +import Config, { StreamrClientOptions } from './Config' import StreamrEthereum from './Ethereum' import Session from './Session' import Connection, { ConnectionError } from './Connection' @@ -14,8 +14,10 @@ import { getUserId } from './user' import { Todo, MaybeAsync } from './types' import { StreamEndpoints } from './rest/StreamEndpoints' import { LoginEndpoints } from './rest/LoginEndpoints' -import { DataUnionEndpoints } from './rest/DataUnionEndpoints' import { DataUnion, DataUnionDeployOptions } from './dataunion/DataUnion' +import { BigNumber } from '@ethersproject/bignumber' +import { getAddress } from '@ethersproject/address' +import { Contract } from '@ethersproject/contracts' // TODO get metadata type from streamr-protocol-js project (it doesn't export the type definitions yet) export type OnMessageCallback = MaybeAsync<(message: any, metadata: any) => void> @@ -32,8 +34,8 @@ export { StreamrClientOptions } class StreamrConnection extends Connection { // TODO define args type when we convert Connection class to TypeScript - constructor(...args: any) { - super(...args) + constructor(options: Todo, client: StreamrClient) { + super(options, client) this.on('message', this.onConnectionMessage) } @@ -137,13 +139,13 @@ function Plugin(targetInstance: any, srcInstance: any) { } // these are mixed in via Plugin function above -interface StreamrClient extends StreamEndpoints, LoginEndpoints, DataUnionEndpoints {} +interface StreamrClient extends StreamEndpoints, LoginEndpoints {} // eslint-disable-next-line no-redeclare class StreamrClient extends EventEmitter { id: string debug: Debug.Debugger - options: StreamrClientConfig + options: StreamrClientOptions session: Session connection: StreamrConnection publisher: Todo @@ -152,18 +154,13 @@ class StreamrClient extends EventEmitter { ethereum: StreamrEthereum streamEndpoints: StreamEndpoints loginEndpoints: LoginEndpoints - dataUnionEndpoints: DataUnionEndpoints constructor(options: Partial = {}, connection?: StreamrConnection) { super() this.id = counterId(`${this.constructor.name}:${uid}`) this.debug = Debug(this.id) - this.options = Config({ - id: this.id, - debug: this.debug, - ...options, - }) + this.options = Config(options) this.debug('new StreamrClient %s: %o', this.id, { version: process.env.version, @@ -182,7 +179,7 @@ class StreamrClient extends EventEmitter { this.on('error', this._onError) // attach before creating sub-components incase they fire error events this.session = new Session(this, this.options.auth) - this.connection = connection || new StreamrConnection(this.options) + this.connection = connection || new StreamrConnection(this.options, this) this.connection .on('connected', this.onConnectionConnected) @@ -195,7 +192,6 @@ class StreamrClient extends EventEmitter { this.streamEndpoints = Plugin(this, new StreamEndpoints(this)) this.loginEndpoints = Plugin(this, new LoginEndpoints(this)) - this.dataUnionEndpoints = Plugin(this, new DataUnionEndpoints(this)) this.cached = new StreamrCached(this) } @@ -380,18 +376,42 @@ class StreamrClient extends EventEmitter { return this.getAddress() } + /** + * Get token balance in "wei" (10^-18 parts) for given address + */ + async getTokenBalance(address: string): Promise { + const { tokenAddress } = this.options + if (!tokenAddress) { + throw new Error('StreamrClient has no tokenAddress configuration.') + } + const addr = getAddress(address) + const provider = this.ethereum.getMainnetProvider() + + const token = new Contract(tokenAddress, [{ + name: 'balanceOf', + inputs: [{ type: 'address' }], + outputs: [{ type: 'uint256' }], + constant: true, + payable: false, + stateMutability: 'view', + type: 'function' + }], provider) + return token.balanceOf(addr) + } + getDataUnion(contractAddress: string) { - return new DataUnion(contractAddress, undefined, this.dataUnionEndpoints) + return DataUnion._fromContractAddress(contractAddress, this) // eslint-disable-line no-underscore-dangle } async deployDataUnion(options?: DataUnionDeployOptions) { - const contract = await this.dataUnionEndpoints.deployDataUnionContract(options) - return new DataUnion(contract.address, contract.sidechain.address, this.dataUnionEndpoints) + return DataUnion._deploy(options, this) // eslint-disable-line no-underscore-dangle } _getDataUnionFromName({ dataUnionName, deployerAddress }: { dataUnionName: string, deployerAddress: string}) { - const contractAddress = this.dataUnionEndpoints.calculateDataUnionMainnetAddress(dataUnionName, deployerAddress) - return this.getDataUnion(contractAddress) + return DataUnion._fromName({ // eslint-disable-line no-underscore-dangle + dataUnionName, + deployerAddress + }, this) } static generateEthereumAccount() { diff --git a/src/dataunion/Contracts.ts b/src/dataunion/Contracts.ts new file mode 100644 index 000000000..e190f44ba --- /dev/null +++ b/src/dataunion/Contracts.ts @@ -0,0 +1,329 @@ +import { getCreate2Address, isAddress } from '@ethersproject/address' +import { arrayify, hexZeroPad } from '@ethersproject/bytes' +import { Contract } from '@ethersproject/contracts' +import { keccak256 } from '@ethersproject/keccak256' +import { defaultAbiCoder } from '@ethersproject/abi' +import { verifyMessage } from '@ethersproject/wallet' +import debug from 'debug' +import { EthereumAddress, Todo } from '../types' +import { dataUnionMainnetABI, dataUnionSidechainABI, factoryMainnetABI, mainnetAmbABI, sidechainAmbABI } from './abi' +import { until } from '../utils' +import { BigNumber } from '@ethersproject/bignumber' +import StreamrEthereum from '../Ethereum' +import StreamrClient from '../StreamrClient' + +const log = debug('StreamrClient::DataUnion') + +export class Contracts { + + ethereum: StreamrEthereum + factoryMainnetAddress: string + factorySidechainAddress: string + cachedSidechainAmb?: Todo + + constructor(client: StreamrClient) { + this.ethereum = client.ethereum + this.factoryMainnetAddress = client.options.factoryMainnetAddress + this.factorySidechainAddress = client.options.factorySidechainAddress + } + + async fetchDataUnionMainnetAddress( + dataUnionName: string, + deployerAddress: EthereumAddress + ): Promise { + const provider = this.ethereum.getMainnetProvider() + const factoryMainnet = new Contract(this.factoryMainnetAddress, factoryMainnetABI, provider) + return factoryMainnet.mainnetAddress(deployerAddress, dataUnionName) + } + + getDataUnionMainnetAddress(dataUnionName: string, deployerAddress: EthereumAddress) { + if (!this.factoryMainnetAddress) { + throw new Error('StreamrClient has no factoryMainnetAddress configuration.') + } + // NOTE! this must be updated when DU sidechain smartcontract changes: keccak256(CloneLib.cloneBytecode(data_union_mainnet_template)); + const codeHash = '0x50a78bac973bdccfc8415d7d9cfd62898b8f7cf6e9b3a15e7d75c0cb820529eb' + const salt = keccak256(defaultAbiCoder.encode(['string', 'address'], [dataUnionName, deployerAddress])) + return getCreate2Address(this.factoryMainnetAddress, salt, codeHash) + } + + async fetchDataUnionSidechainAddress(duMainnetAddress: EthereumAddress): Promise { + const provider = this.ethereum.getMainnetProvider() + const factoryMainnet = new Contract(this.factoryMainnetAddress, factoryMainnetABI, provider) + return factoryMainnet.sidechainAddress(duMainnetAddress) + } + + getDataUnionSidechainAddress(mainnetAddress: EthereumAddress) { + if (!this.factorySidechainAddress) { + throw new Error('StreamrClient has no factorySidechainAddress configuration.') + } + // NOTE! this must be updated when DU sidechain smartcontract changes: keccak256(CloneLib.cloneBytecode(data_union_sidechain_template)) + const codeHash = '0x040cf686e25c97f74a23a4bf01c29dd77e260c4b694f5611017ce9713f58de83' + return getCreate2Address(this.factorySidechainAddress, hexZeroPad(mainnetAddress, 32), codeHash) + } + + getMainnetContractReadOnly(contractAddress: EthereumAddress) { + if (isAddress(contractAddress)) { + const provider = this.ethereum.getMainnetProvider() + return new Contract(contractAddress, dataUnionMainnetABI, provider) + } + throw new Error(`${contractAddress} was not a good Ethereum address`) + } + + getMainnetContract(contractAddress: EthereumAddress) { + const du = this.getMainnetContractReadOnly(contractAddress) + const signer = this.ethereum.getSigner() + return du.connect(signer) + } + + async getSidechainContract(contractAddress: EthereumAddress) { + const signer = await this.ethereum.getSidechainSigner() + const duMainnet = this.getMainnetContractReadOnly(contractAddress) + const duSidechainAddress = this.getDataUnionSidechainAddress(duMainnet.address) + const duSidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, signer) + return duSidechain + } + + async getSidechainContractReadOnly(contractAddress: EthereumAddress) { + const provider = this.ethereum.getSidechainProvider() + const duMainnet = this.getMainnetContractReadOnly(contractAddress) + const duSidechainAddress = this.getDataUnionSidechainAddress(duMainnet.address) + const duSidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, provider) + return duSidechain + } + + // Find the Asyncronous Message-passing Bridge sidechain ("home") contract + async getSidechainAmb() { + if (!this.cachedSidechainAmb) { + const getAmbPromise = async () => { + const mainnetProvider = this.ethereum.getMainnetProvider() + const factoryMainnet = new Contract(this.factoryMainnetAddress, factoryMainnetABI, mainnetProvider) + const sidechainProvider = this.ethereum.getSidechainProvider() + const factorySidechainAddress = await factoryMainnet.data_union_sidechain_factory() // TODO use getDataUnionSidechainAddress() + const factorySidechain = new Contract(factorySidechainAddress, [{ + name: 'amb', + inputs: [], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' + }], sidechainProvider) + const sidechainAmbAddress = await factorySidechain.amb() + return new Contract(sidechainAmbAddress, sidechainAmbABI, sidechainProvider) + } + this.cachedSidechainAmb = getAmbPromise() + this.cachedSidechainAmb = await this.cachedSidechainAmb // eslint-disable-line require-atomic-updates + } + return this.cachedSidechainAmb + } + + async getMainnetAmb() { + const mainnetProvider = this.ethereum.getMainnetProvider() + const factoryMainnet = new Contract(this.factoryMainnetAddress, factoryMainnetABI, mainnetProvider) + const mainnetAmbAddress = await factoryMainnet.amb() + return new Contract(mainnetAmbAddress, mainnetAmbABI, mainnetProvider) + } + + async requiredSignaturesHaveBeenCollected(messageHash: Todo) { + const sidechainAmb = await this.getSidechainAmb() + const requiredSignatureCount = await sidechainAmb.requiredSignatures() + + // Bit 255 is set to mark completion, double check though + const sigCountStruct = await sidechainAmb.numMessagesSigned(messageHash) + const collectedSignatureCount = sigCountStruct.mask(255) + const markedComplete = sigCountStruct.shr(255).gt(0) + + log(`${collectedSignatureCount.toString()} out of ${requiredSignatureCount.toString()} collected`) + if (markedComplete) { log('All signatures collected') } + return markedComplete + } + + // move signatures from sidechain to mainnet + async transportSignatures(messageHash: string) { + const sidechainAmb = await this.getSidechainAmb() + const message = await sidechainAmb.message(messageHash) + const messageId = '0x' + message.substr(2, 64) + const sigCountStruct = await sidechainAmb.numMessagesSigned(messageHash) + const collectedSignatureCount = sigCountStruct.mask(255).toNumber() + + log(`${collectedSignatureCount} signatures reported, getting them from the sidechain AMB...`) + const signatures = await Promise.all(Array(collectedSignatureCount).fill(0).map(async (_, i) => sidechainAmb.signature(messageHash, i))) + + const [vArray, rArray, sArray]: Todo = [[], [], []] + signatures.forEach((signature: string, i) => { + log(` Signature ${i}: ${signature} (len=${signature.length}=${signature.length / 2 - 1} bytes)`) + rArray.push(signature.substr(2, 64)) + sArray.push(signature.substr(66, 64)) + vArray.push(signature.substr(130, 2)) + }) + const packedSignatures = BigNumber.from(signatures.length).toHexString() + vArray.join('') + rArray.join('') + sArray.join('') + log(`All signatures packed into one: ${packedSignatures}`) + + // Gas estimation also checks that the transaction would succeed, and provides a helpful error message in case it would fail + const mainnetAmb = await this.getMainnetAmb() + log(`Estimating gas using mainnet AMB @ ${mainnetAmb.address}, message=${message}`) + let gasLimit + try { + // magic number suggested by https://github.com/poanetwork/tokenbridge/blob/master/oracle/src/utils/constants.js + gasLimit = BigNumber.from(await mainnetAmb.estimateGas.executeSignatures(message, packedSignatures)).add(200000) + log(`Calculated gas limit: ${gasLimit.toString()}`) + } catch (e) { + // Failure modes from https://github.com/poanetwork/tokenbridge/blob/master/oracle/src/events/processAMBCollectedSignatures/estimateGas.js + log('Gas estimation failed: Check if the message was already processed') + const alreadyProcessed = await mainnetAmb.relayedMessages(messageId) + if (alreadyProcessed) { + log(`WARNING: Tried to transport signatures but they have already been transported (Message ${messageId} has already been processed)`) + log('This could happen if payForSignatureTransport=true, but bridge operator also pays for signatures, and got there before your client') + return null + } + + log('Gas estimation failed: Check if number of signatures is enough') + const mainnetProvider = this.ethereum.getMainnetProvider() + const validatorContractAddress = await mainnetAmb.validatorContract() + const validatorContract = new Contract(validatorContractAddress, [{ + name: 'isValidator', + inputs: [{ type: 'address' }], + outputs: [{ type: 'bool' }], + stateMutability: 'view', + type: 'function' + }, { + name: 'requiredSignatures', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }], mainnetProvider) + const requiredSignatures = await validatorContract.requiredSignatures() + if (requiredSignatures.gt(signatures.length)) { + throw new Error('The number of required signatures does not match between sidechain(' + + signatures.length + ' and mainnet( ' + requiredSignatures.toString()) + } + + log('Gas estimation failed: Check if all the signatures were made by validators') + log(` Recover signer addresses from signatures [${signatures.join(', ')}]`) + const signers = signatures.map((signature) => verifyMessage(arrayify(message), signature)) + log(` Check that signers are validators [[${signers.join(', ')}]]`) + const isValidatorArray = await Promise.all(signers.map((address) => [address, validatorContract.isValidator(address)])) + const nonValidatorSigners = isValidatorArray.filter(([, isValidator]) => !isValidator) + if (nonValidatorSigners.length > 0) { + throw new Error(`Following signers are not listed as validators in mainnet validator contract at ${validatorContractAddress}:\n - ` + + nonValidatorSigners.map(([address]) => address).join('\n - ')) + } + + throw new Error(`Gas estimation failed: Unknown error while processing message ${message} with ${e.stack}`) + } + + const signer = this.ethereum.getSigner() + log(`Sending message from signer=${await signer.getAddress()}`) + const txAMB = await mainnetAmb.connect(signer).executeSignatures(message, packedSignatures) + const trAMB = await txAMB.wait() + return trAMB + } + + async payForSignatureTransport(tr: { events: any[] }, options: { pollingIntervalMs?: number, retryTimeoutMs?: number } = {}) { + const { + pollingIntervalMs = 1000, + retryTimeoutMs = 60000, + } = options + log(`Got receipt, filtering UserRequestForSignature from ${tr.events.length} events...`) + // event UserRequestForSignature(bytes32 indexed messageId, bytes encodedData); + const sigEventArgsArray = tr.events.filter((e: Todo) => e.event === 'UserRequestForSignature').map((e: Todo) => e.args) + if (sigEventArgsArray.length < 1) { + throw new Error("No UserRequestForSignature events emitted from withdraw transaction, can't transport withdraw to mainnet") + } + /* eslint-disable no-await-in-loop */ + // eslint-disable-next-line no-restricted-syntax + for (const eventArgs of sigEventArgsArray) { + const messageId = eventArgs[0] + const messageHash = keccak256(eventArgs[1]) + + log(`Waiting until sidechain AMB has collected required signatures for hash=${messageHash}...`) + await until(async () => this.requiredSignaturesHaveBeenCollected(messageHash), pollingIntervalMs, retryTimeoutMs) + + log(`Checking mainnet AMB hasn't already processed messageId=${messageId}`) + const mainnetAmb = await this.getMainnetAmb() + const alreadySent = await mainnetAmb.messageCallStatus(messageId) + const failAddress = await mainnetAmb.failedMessageSender(messageId) + if (alreadySent || failAddress !== '0x0000000000000000000000000000000000000000') { // zero address means no failed messages + log(`WARNING: Mainnet bridge has already processed withdraw messageId=${messageId}`) + log([ + 'This could happen if payForSignatureTransport=true, but bridge operator also pays for', + 'signatures, and got there before your client', + ].join(' ')) + continue + } + + log(`Transporting signatures for hash=${messageHash}`) + await this.transportSignatures(messageHash) + } + /* eslint-enable no-await-in-loop */ + } + + async deployDataUnion({ + ownerAddress, + agentAddressList, + duName, + deployerAddress, + adminFeeBN, + sidechainRetryTimeoutMs, + sidechainPollingIntervalMs, + confirmations, + gasPrice + }: { + ownerAddress: EthereumAddress, + agentAddressList: EthereumAddress[] + duName: string + deployerAddress: EthereumAddress + adminFeeBN: BigNumber + sidechainRetryTimeoutMs: number + sidechainPollingIntervalMs: number + confirmations: number + gasPrice?: BigNumber, + }) { + const mainnetProvider = this.ethereum.getMainnetProvider() + const mainnetWallet = this.ethereum.getSigner() + const sidechainProvider = this.ethereum.getSidechainProvider() + + const duMainnetAddress = await this.fetchDataUnionMainnetAddress(duName, deployerAddress) + const duSidechainAddress = await this.fetchDataUnionSidechainAddress(duMainnetAddress) + + if (await mainnetProvider.getCode(duMainnetAddress) !== '0x') { + throw new Error(`Mainnet data union "${duName}" contract ${duMainnetAddress} already exists!`) + } + + if (!isAddress(this.factoryMainnetAddress)) { + throw new Error('StreamrClient has invalid factoryMainnetAddress configuration.') + } + + if (await mainnetProvider.getCode(this.factoryMainnetAddress) === '0x') { + throw new Error(`Data union factory contract not found at ${this.factoryMainnetAddress}, check StreamrClient.options.factoryMainnetAddress!`) + } + + const factoryMainnet = new Contract(this.factoryMainnetAddress!, factoryMainnetABI, mainnetWallet) + const ethersOptions: any = {} + if (gasPrice) { + ethersOptions.gasPrice = gasPrice + } + const tx = await factoryMainnet.deployNewDataUnion( + ownerAddress, + adminFeeBN, + agentAddressList, + duName, + ethersOptions + ) + const tr = await tx.wait(confirmations) + + log(`Data Union "${duName}" (mainnet: ${duMainnetAddress}, sidechain: ${duSidechainAddress}) deployed to mainnet, waiting for side-chain...`) + await until( + async () => await sidechainProvider.getCode(duSidechainAddress) !== '0x', + sidechainRetryTimeoutMs, + sidechainPollingIntervalMs + ) + + const dataUnion = new Contract(duMainnetAddress, dataUnionMainnetABI, mainnetWallet) + // @ts-expect-error + dataUnion.deployTxReceipt = tr + // @ts-expect-error + dataUnion.sidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, sidechainProvider) + return dataUnion + + } +} diff --git a/src/dataunion/DataUnion.ts b/src/dataunion/DataUnion.ts index a336f2f16..70d72c1a4 100644 --- a/src/dataunion/DataUnion.ts +++ b/src/dataunion/DataUnion.ts @@ -1,9 +1,18 @@ +import { getAddress } from '@ethersproject/address' import { BigNumber } from '@ethersproject/bignumber' -import { DataUnionEndpoints } from '../rest/DataUnionEndpoints' +import { arrayify, hexZeroPad } from '@ethersproject/bytes' +import { Contract } from '@ethersproject/contracts' +import { TransactionReceipt, TransactionResponse } from '@ethersproject/providers' +import debug from 'debug' +import { Contracts } from './Contracts' +import StreamrClient from '../StreamrClient' +import { EthereumAddress, Todo } from '../types' +import { until, getEndpointUrl } from '../utils' +import authFetch from '../rest/authFetch' export interface DataUnionDeployOptions { - owner?: string, - joinPartAgents?: string[], + owner?: EthereumAddress, + joinPartAgents?: EthereumAddress[], dataUnionName?: string, adminFee?: number, sidechainPollingIntervalMs?: number, @@ -33,16 +42,41 @@ export interface DataUnionMemberListModificationOptions { confirmations?: number } +export interface DataUnionStats { + activeMemberCount: BigNumber, + inactiveMemberCount: BigNumber, + joinPartAgentCount: BigNumber, + totalEarnings: BigNumber, + totalWithdrawable: BigNumber, + lifetimeMemberEarnings: BigNumber +} + +export enum MemberStatus { + ACTIVE = 'ACTIVE', + INACTIVE = 'INACTIVE', + NONE = 'NONE', +} + +export interface MemberStats { + status: MemberStatus + earningsBeforeLastJoin: BigNumber + totalEarnings: BigNumber + withdrawableEarnings: BigNumber +} + +const log = debug('StreamrClient::DataUnion') + export class DataUnion { - contractAddress: string - sidechainAddress: string - dataUnionEndpoints: DataUnionEndpoints + contractAddress: EthereumAddress + sidechainAddress: EthereumAddress + client: StreamrClient - constructor(contractAddress: string, sidechainAddress: string|undefined, dataUnionEndpoints: DataUnionEndpoints) { - this.contractAddress = contractAddress - this.sidechainAddress = sidechainAddress || dataUnionEndpoints.calculateDataUnionSidechainAddress(contractAddress) - this.dataUnionEndpoints = dataUnionEndpoints + constructor(contractAddress: EthereumAddress, sidechainAddress: EthereumAddress, client: StreamrClient) { + // validate and convert to checksum case + this.contractAddress = getAddress(contractAddress) + this.sidechainAddress = getAddress(sidechainAddress) + this.client = client } getAddress() { @@ -55,85 +89,474 @@ export class DataUnion { // Member functions - async join(secret?: string) { - return this.dataUnionEndpoints.join(secret, this.contractAddress) + /** + * Send a joinRequest, or get into data union instantly with a data union secret + */ + async join(secret?: string): Promise { + const memberAddress = this.client.getAddress() as string + const body: any = { + memberAddress + } + if (secret) { body.secret = secret } + + const url = getEndpointUrl(this.client.options.restUrl, 'dataunions', this.contractAddress, 'joinRequests') + const response = await authFetch( + url, + this.client.session, + { + method: 'POST', + body: JSON.stringify(body), + headers: { + 'Content-Type': 'application/json', + }, + }, + ) + if (secret) { + await until(async () => this.isMember(memberAddress)) + } + return response } - async isMember(memberAddress: string) { - return this.dataUnionEndpoints.isMember(memberAddress, this.contractAddress) + async isMember(memberAddress: EthereumAddress): Promise { + const address = getAddress(memberAddress) + const duSidechain = await this.getContracts().getSidechainContractReadOnly(this.contractAddress) + const ACTIVE = 1 // memberData[0] is enum ActiveStatus {None, Active, Inactive} + const memberData = await duSidechain.memberData(address) + const state = memberData[0] + return (state === ACTIVE) } - async withdrawAll(options?: DataUnionWithdrawOptions) { - return this.dataUnionEndpoints.withdrawAll(this.contractAddress, options) + /** + * Withdraw all your earnings + * @returns receipt once withdraw is complete (tokens are seen in mainnet) + */ + async withdrawAll(options?: DataUnionWithdrawOptions): Promise { + const recipientAddress = this.client.getAddress() + return this._executeWithdraw( + () => this.getWithdrawAllTx(), + recipientAddress, + options + ) } - async withdrawAllTo(recipientAddress: string, options?: DataUnionWithdrawOptions) { - return this.dataUnionEndpoints.withdrawAllTo(recipientAddress, options, this.contractAddress) + /** + * Get the tx promise for withdrawing all your earnings + * @returns await on call .wait to actually send the tx + */ + private async getWithdrawAllTx(): Promise { + const signer = await this.client.ethereum.getSidechainSigner() + const address = await signer.getAddress() + const duSidechain = await this.getContracts().getSidechainContract(this.contractAddress) + + const withdrawable = await duSidechain.getWithdrawableEarnings(address) + if (withdrawable.eq(0)) { + throw new Error(`${address} has nothing to withdraw in (sidechain) data union ${duSidechain.address}`) + } + + if (this.client.options.minimumWithdrawTokenWei && withdrawable.lt(this.client.options.minimumWithdrawTokenWei)) { + throw new Error(`${address} has only ${withdrawable} to withdraw in ` + + `(sidechain) data union ${duSidechain.address} (min: ${this.client.options.minimumWithdrawTokenWei})`) + } + return duSidechain.withdrawAll(address, true) // sendToMainnet=true } - async signWithdrawAllTo(recipientAddress: string) { - return this.dataUnionEndpoints.signWithdrawAllTo(recipientAddress, this.contractAddress) + /** + * Withdraw earnings and "donate" them to the given address + * @returns get receipt once withdraw is complete (tokens are seen in mainnet) + */ + async withdrawAllTo( + recipientAddress: EthereumAddress, + options?: DataUnionWithdrawOptions + ): Promise { + const to = getAddress(recipientAddress) // throws if bad address + return this._executeWithdraw( + () => this.getWithdrawAllToTx(to), + to, + options + ) } - async signWithdrawAmountTo(recipientAddress: string, amountTokenWei: BigNumber|number|string) { - return this.dataUnionEndpoints.signWithdrawAmountTo(recipientAddress, amountTokenWei, this.contractAddress) + /** + * Withdraw earnings and "donate" them to the given address + * @param recipientAddress - the address to receive the tokens + * @returns await on call .wait to actually send the tx + */ + private async getWithdrawAllToTx(recipientAddress: EthereumAddress): Promise { + const signer = await this.client.ethereum.getSidechainSigner() + const address = await signer.getAddress() + const duSidechain = await this.getContracts().getSidechainContract(this.contractAddress) + const withdrawable = await duSidechain.getWithdrawableEarnings(address) + if (withdrawable.eq(0)) { + throw new Error(`${address} has nothing to withdraw in (sidechain) data union ${duSidechain.address}`) + } + return duSidechain.withdrawAllTo(recipientAddress, true) // sendToMainnet=true + } + + /** + * Member can sign off to "donate" all earnings to another address such that someone else + * can submit the transaction (and pay for the gas) + * This signature is only valid until next withdrawal takes place (using this signature or otherwise). + * Note that while it's a "blank cheque" for withdrawing all earnings at the moment it's used, it's + * invalidated by the first withdraw after signing it. In other words, any signature can be invalidated + * by making a "normal" withdraw e.g. `await streamrClient.withdrawAll()` + * Admin can execute the withdraw using this signature: ``` + * await adminStreamrClient.withdrawAllToSigned(memberAddress, recipientAddress, signature) + * ``` + * @param recipientAddress - the address authorized to receive the tokens + * @returns signature authorizing withdrawing all earnings to given recipientAddress + */ + async signWithdrawAllTo(recipientAddress: EthereumAddress): Promise { + return this.signWithdrawAmountTo(recipientAddress, BigNumber.from(0)) + } + + /** + * Member can sign off to "donate" specific amount of earnings to another address such that someone else + * can submit the transaction (and pay for the gas) + * This signature is only valid until next withdrawal takes place (using this signature or otherwise). + * @param recipientAddress - the address authorized to receive the tokens + * @param amountTokenWei - that the signature is for (can't be used for less or for more) + * @returns signature authorizing withdrawing all earnings to given recipientAddress + */ + async signWithdrawAmountTo( + recipientAddress: EthereumAddress, + amountTokenWei: BigNumber|number|string + ): Promise { + const to = getAddress(recipientAddress) // throws if bad address + const signer = this.client.ethereum.getSigner() // it shouldn't matter if it's mainnet or sidechain signer since key should be the same + const address = await signer.getAddress() + const duSidechain = await this.getContracts().getSidechainContractReadOnly(this.contractAddress) + const memberData = await duSidechain.memberData(address) + if (memberData[0] === '0') { throw new Error(`${address} is not a member in Data Union (sidechain address ${duSidechain.address})`) } + const withdrawn = memberData[3] + // @ts-expect-error + const message = to + hexZeroPad(amountTokenWei, 32).slice(2) + duSidechain.address.slice(2) + hexZeroPad(withdrawn, 32).slice(2) + const signature = await signer.signMessage(arrayify(message)) + return signature } // Query functions - async getStats() { - return this.dataUnionEndpoints.getStats(this.contractAddress) + async getStats(): Promise { + const duSidechain = await this.getContracts().getSidechainContractReadOnly(this.contractAddress) + const [ + totalEarnings, + totalEarningsWithdrawn, + activeMemberCount, + inactiveMemberCount, + lifetimeMemberEarnings, + joinPartAgentCount, + ] = await duSidechain.getStats() + const totalWithdrawable = totalEarnings.sub(totalEarningsWithdrawn) + return { + activeMemberCount, + inactiveMemberCount, + joinPartAgentCount, + totalEarnings, + totalWithdrawable, + lifetimeMemberEarnings, + } } - async getMemberStats(memberAddress: string) { - return this.dataUnionEndpoints.getMemberStats(memberAddress, this.contractAddress) + /** + * Get stats of a single data union member + */ + async getMemberStats(memberAddress: EthereumAddress): Promise { + const address = getAddress(memberAddress) + // TODO: use duSidechain.getMemberStats(address) once it's implemented, to ensure atomic read + // (so that memberData is from same block as getEarnings, otherwise withdrawable will be foobar) + const duSidechain = await this.getContracts().getSidechainContractReadOnly(this.contractAddress) + const mdata = await duSidechain.memberData(address) + const total = await duSidechain.getEarnings(address).catch(() => BigNumber.from(0)) + const withdrawnEarnings = mdata[3] + const withdrawable = total ? total.sub(withdrawnEarnings) : BigNumber.from(0) + const STATUSES = [MemberStatus.NONE, MemberStatus.ACTIVE, MemberStatus.INACTIVE] + return { + status: STATUSES[mdata[0]], + earningsBeforeLastJoin: mdata[1], + totalEarnings: total, + withdrawableEarnings: withdrawable, + } } - async getWithdrawableEarnings(memberAddress: string) { - return this.dataUnionEndpoints.getWithdrawableEarnings(memberAddress, this.contractAddress) + /** + * Get the amount of tokens the member would get from a successful withdraw + */ + async getWithdrawableEarnings(memberAddress: EthereumAddress): Promise { + const address = getAddress(memberAddress) + const duSidechain = await this.getContracts().getSidechainContractReadOnly(this.contractAddress) + return duSidechain.getWithdrawableEarnings(address) } - async getAdminFee() { - return this.dataUnionEndpoints.getAdminFee(this.contractAddress) + /** + * Get data union admin fee fraction (between 0.0 and 1.0) that admin gets from each revenue event + */ + async getAdminFee(): Promise { + const duMainnet = this.getContracts().getMainnetContractReadOnly(this.contractAddress) + const adminFeeBN = await duMainnet.adminFeeFraction() + return +adminFeeBN.toString() / 1e18 } - async getAdminAddress() { - return this.dataUnionEndpoints.getAdminAddress(this.contractAddress) + async getAdminAddress(): Promise { + const duMainnet = this.getContracts().getMainnetContractReadOnly(this.contractAddress) + return duMainnet.owner() } - async getVersion() { - return this.dataUnionEndpoints.getVersion(this.contractAddress) + /** + * Figure out if given mainnet address is old DataUnion (v 1.0) or current 2.0 + * NOTE: Current version of streamr-client-javascript can only handle current version! + */ + async getVersion(): Promise { + const provider = this.client.ethereum.getMainnetProvider() + const du = new Contract(this.contractAddress, [{ + name: 'version', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' + }], provider) + try { + const version = await du.version() + return +version + } catch (e) { + // "not a data union" + return 0 + } } // Admin functions - async createSecret(name: string = 'Untitled Data Union Secret') { - return this.dataUnionEndpoints.createSecret(this.contractAddress, name) + /** + * Add a new data union secret + */ + async createSecret(name: string = 'Untitled Data Union Secret'): Promise { + const url = getEndpointUrl(this.client.options.restUrl, 'dataunions', this.contractAddress, 'secrets') + const res = await authFetch( + url, + this.client.session, + { + method: 'POST', + body: JSON.stringify({ + name + }), + headers: { + 'Content-Type': 'application/json', + }, + }, + ) + return res.secret + } + + /** + * Add given Ethereum addresses as data union members + */ + async addMembers( + memberAddressList: EthereumAddress[], + options: DataUnionMemberListModificationOptions = {} + ): Promise { + const members = memberAddressList.map(getAddress) // throws if there are bad addresses + const duSidechain = await this.getContracts().getSidechainContract(this.contractAddress) + const tx = await duSidechain.addMembers(members) + // TODO: wrap promise for better error reporting in case tx fails (parse reason, throw proper error) + const { confirmations = 1 } = options + return tx.wait(confirmations) } - async addMembers(memberAddressList: string[], options?: DataUnionMemberListModificationOptions) { - return this.dataUnionEndpoints.addMembers(memberAddressList, options, this.contractAddress) + /** + * Remove given members from data union + */ + async removeMembers( + memberAddressList: EthereumAddress[], + options: DataUnionMemberListModificationOptions = {}, + ): Promise { + const members = memberAddressList.map(getAddress) // throws if there are bad addresses + const duSidechain = await this.getContracts().getSidechainContract(this.contractAddress) + const tx = await duSidechain.partMembers(members) + // TODO: wrap promise for better error reporting in case tx fails (parse reason, throw proper error) + const { confirmations = 1 } = options + return tx.wait(confirmations) } - async removeMembers(memberAddressList: string[], options?: DataUnionMemberListModificationOptions) { - return this.dataUnionEndpoints.removeMembers(memberAddressList, options, this.contractAddress) + /** + * Admin: withdraw earnings (pay gas) on behalf of a member + * TODO: add test + * @param memberAddress - the other member who gets their tokens out of the Data Union + * @returns Receipt once withdraw transaction is confirmed + */ + async withdrawAllToMember( + memberAddress: EthereumAddress, + options?: DataUnionWithdrawOptions + ): Promise { + const address = getAddress(memberAddress) // throws if bad address + return this._executeWithdraw( + () => this.getWithdrawAllToMemberTx(address), + address, + options + ) } - async withdrawAllToMember(memberAddress: string, options?: DataUnionWithdrawOptions) { - return this.dataUnionEndpoints.withdrawAllToMember(memberAddress, options, this.contractAddress) + /** + * Admin: get the tx promise for withdrawing all earnings on behalf of a member + * @param memberAddress - the other member who gets their tokens out of the Data Union + * @returns await on call .wait to actually send the tx + */ + private async getWithdrawAllToMemberTx(memberAddress: EthereumAddress): Promise { + const a = getAddress(memberAddress) // throws if bad address + const duSidechain = await this.getContracts().getSidechainContract(this.contractAddress) + return duSidechain.withdrawAll(a, true) // sendToMainnet=true } - async withdrawAllToSigned(memberAddress: string, recipientAddress: string, signature: string, options?: DataUnionWithdrawOptions) { - return this.dataUnionEndpoints.withdrawAllToSigned(memberAddress, recipientAddress, signature, options, this.contractAddress) + /** + * Admin: Withdraw a member's earnings to another address, signed by the member + * @param memberAddress - the member whose earnings are sent out + * @param recipientAddress - the address to receive the tokens in mainnet + * @param signature - from member, produced using signWithdrawAllTo + * @returns receipt once withdraw transaction is confirmed + */ + async withdrawAllToSigned( + memberAddress: EthereumAddress, + recipientAddress: EthereumAddress, + signature: string, + options?: DataUnionWithdrawOptions + ): Promise { + const from = getAddress(memberAddress) // throws if bad address + const to = getAddress(recipientAddress) + return this._executeWithdraw( + () => this.getWithdrawAllToSignedTx(from, to, signature), + to, + options + ) } - async setAdminFee(newFeeFraction: number) { - return this.dataUnionEndpoints.setAdminFee(newFeeFraction, this.contractAddress) + /** + * Admin: Withdraw a member's earnings to another address, signed by the member + * @param memberAddress - the member whose earnings are sent out + * @param recipientAddress - the address to receive the tokens in mainnet + * @param signature - from member, produced using signWithdrawAllTo + * @returns await on call .wait to actually send the tx + */ + private async getWithdrawAllToSignedTx( + memberAddress: EthereumAddress, + recipientAddress: EthereumAddress, + signature: string, + ): Promise { + const duSidechain = await this.getContracts().getSidechainContract(this.contractAddress) + return duSidechain.withdrawAllToSigned(memberAddress, recipientAddress, true, signature) // sendToMainnet=true + } + + /** + * Admin: set admin fee (between 0.0 and 1.0) for the data union + */ + async setAdminFee(newFeeFraction: number): Promise { + if (newFeeFraction < 0 || newFeeFraction > 1) { + throw new Error('newFeeFraction argument must be a number between 0...1, got: ' + newFeeFraction) + } + const adminFeeBN = BigNumber.from((newFeeFraction * 1e18).toFixed()) // last 2...3 decimals are going to be gibberish + const duMainnet = this.getContracts().getMainnetContract(this.contractAddress) + const tx = await duMainnet.setAdminFee(adminFeeBN) + return tx.wait() + } + + /** + * Create a new DataUnionMainnet contract to mainnet with DataUnionFactoryMainnet + * This triggers DataUnionSidechain contract creation in sidechain, over the bridge (AMB) + * @return that resolves when the new DU is deployed over the bridge to side-chain + */ + static async _deploy(options: DataUnionDeployOptions = {}, client: StreamrClient): Promise { + const deployerAddress = client.getAddress() + const { + owner, + joinPartAgents, + dataUnionName, + adminFee = 0, + sidechainPollingIntervalMs = 1000, + sidechainRetryTimeoutMs = 600000, + confirmations = 1, + gasPrice + } = options + + let duName = dataUnionName + if (!duName) { + duName = `DataUnion-${Date.now()}` // TODO: use uuid + log(`dataUnionName generated: ${duName}`) + } + + if (adminFee < 0 || adminFee > 1) { throw new Error('options.adminFeeFraction must be a number between 0...1, got: ' + adminFee) } + const adminFeeBN = BigNumber.from((adminFee * 1e18).toFixed()) // last 2...3 decimals are going to be gibberish + + const ownerAddress = (owner) ? getAddress(owner) : deployerAddress + + let agentAddressList + if (Array.isArray(joinPartAgents)) { + // getAddress throws if there's an invalid address in the array + agentAddressList = joinPartAgents.map(getAddress) + } else { + // streamrNode needs to be joinPartAgent so that EE join with secret works (and join approvals from Marketplace UI) + agentAddressList = [ownerAddress] + if (client.options.streamrNodeAddress) { + agentAddressList.push(getAddress(client.options.streamrNodeAddress)) + } + } + + const contract = await new Contracts(client).deployDataUnion({ + ownerAddress, + agentAddressList, + duName, + deployerAddress, + adminFeeBN, + sidechainRetryTimeoutMs, + sidechainPollingIntervalMs, + confirmations, + gasPrice + }) + return new DataUnion(contract.address, contract.sidechain.address, client) } // Internal functions + static _fromContractAddress(contractAddress: string, client: StreamrClient) { + const contracts = new Contracts(client) + const sidechainAddress = contracts.getDataUnionSidechainAddress(getAddress(contractAddress)) // throws if bad address + return new DataUnion(contractAddress, sidechainAddress, client) + } + + static _fromName({ dataUnionName, deployerAddress }: { dataUnionName: string, deployerAddress: string}, client: StreamrClient) { + const contracts = new Contracts(client) + const contractAddress = contracts.getDataUnionMainnetAddress(dataUnionName, getAddress(deployerAddress)) // throws if bad address + return DataUnion._fromContractAddress(contractAddress, client) // eslint-disable-line no-underscore-dangle + } + async _getContract() { - return this.dataUnionEndpoints.getContract(this.contractAddress) + const ret = this.getContracts().getMainnetContract(this.contractAddress) + // @ts-expect-error + ret.sidechain = await this.getContracts().getSidechainContract(this.contractAddress) + return ret + } + + private getContracts() { + return new Contracts(this.client) + } + + // template for withdraw functions + // client could be replaced with AMB (mainnet and sidechain) + private async _executeWithdraw( + getWithdrawTxFunc: () => Promise, + recipientAddress: EthereumAddress, + options: DataUnionWithdrawOptions = {} + ): Promise { + const { + pollingIntervalMs = 1000, + retryTimeoutMs = 60000, + payForSignatureTransport = this.client.options.payForSignatureTransport + }: any = options + const getBalanceFunc = () => this.client.getTokenBalance(recipientAddress) + const balanceBefore = await getBalanceFunc() + const tx = await getWithdrawTxFunc() + const tr = await tx.wait() + if (payForSignatureTransport) { + await this.getContracts().payForSignatureTransport(tr, options) + } + log(`Waiting for balance ${balanceBefore.toString()} to change`) + await until(async () => !(await getBalanceFunc()).eq(balanceBefore), retryTimeoutMs, pollingIntervalMs) + return tr } } diff --git a/src/dataunion/abi.ts b/src/dataunion/abi.ts new file mode 100644 index 000000000..df28546ac --- /dev/null +++ b/src/dataunion/abi.ts @@ -0,0 +1,223 @@ +export const dataUnionMainnetABI = [{ + name: 'sendTokensToBridge', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'token', + inputs: [], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'owner', + inputs: [], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'setAdminFee', + inputs: [{ type: 'uint256' }], + outputs: [], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'adminFeeFraction', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}] + +export const dataUnionSidechainABI = [{ + name: 'addMembers', + inputs: [{ type: 'address[]', internalType: 'address payable[]', }], + outputs: [], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'partMembers', + inputs: [{ type: 'address[]' }], + outputs: [], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'withdrawAll', + inputs: [{ type: 'address' }, { type: 'bool' }], + outputs: [{ type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'withdrawAllTo', + inputs: [{ type: 'address' }, { type: 'bool' }], + outputs: [{ type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'withdrawAllToSigned', + inputs: [{ type: 'address' }, { type: 'address' }, { type: 'bool' }, { type: 'bytes' }], + outputs: [{ type: 'uint256' }], + stateMutability: 'nonpayable', + type: 'function' +}, { + // enum ActiveStatus {None, Active, Inactive, Blocked} + // struct MemberInfo { + // ActiveStatus status; + // uint256 earnings_before_last_join; + // uint256 lme_at_join; + // uint256 withdrawnEarnings; + // } + name: 'memberData', + inputs: [{ type: 'address' }], + outputs: [{ type: 'uint8' }, { type: 'uint256' }, { type: 'uint256' }, { type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + inputs: [], + name: 'getStats', + outputs: [{ type: 'uint256[6]' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'getEarnings', + inputs: [{ type: 'address' }], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'getWithdrawableEarnings', + inputs: [{ type: 'address' }], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'lifetimeMemberEarnings', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'totalWithdrawable', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'totalEarnings', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'activeMemberCount', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + // this event is emitted by withdrawing process, + // see https://github.com/poanetwork/tokenbridge-contracts/blob/master/contracts/upgradeable_contracts/arbitrary_message/HomeAMB.sol + name: 'UserRequestForSignature', + inputs: [ + { indexed: true, name: 'messageId', type: 'bytes32' }, + { indexed: false, name: 'encodedData', type: 'bytes' } + ], + anonymous: false, + type: 'event' +}] + +// Only the part of ABI that is needed by deployment (and address resolution) +export const factoryMainnetABI = [{ + type: 'constructor', + inputs: [{ type: 'address' }, { type: 'address' }, { type: 'address' }, { type: 'address' }, { type: 'uint256' }], + stateMutability: 'nonpayable' +}, { + name: 'sidechainAddress', + inputs: [{ type: 'address' }], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'mainnetAddress', + inputs: [{ type: 'address' }, { type: 'string' }], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'deployNewDataUnion', + inputs: [{ type: 'address' }, { type: 'uint256' }, { type: 'address[]' }, { type: 'string' }], + outputs: [{ type: 'address' }], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'amb', + inputs: [], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'data_union_sidechain_factory', + inputs: [], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}] + +export const mainnetAmbABI = [{ + name: 'executeSignatures', + inputs: [{ type: 'bytes' }, { type: 'bytes' }], // data, signatures + outputs: [], + stateMutability: 'nonpayable', + type: 'function' +}, { + name: 'messageCallStatus', + inputs: [{ type: 'bytes32' }], // messageId + outputs: [{ type: 'bool' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'failedMessageSender', + inputs: [{ type: 'bytes32' }], // messageId + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'relayedMessages', + inputs: [{ type: 'bytes32' }], // messageId, was called "_txhash" though?! + outputs: [{ name: '', type: 'bool' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'validatorContract', + inputs: [], + outputs: [{ type: 'address' }], + stateMutability: 'view', + type: 'function' +}] + +export const sidechainAmbABI = [{ + name: 'signature', + inputs: [{ type: 'bytes32' }, { type: 'uint256' }], // messageHash, index + outputs: [{ type: 'bytes' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'message', + inputs: [{ type: 'bytes32' }], // messageHash + outputs: [{ type: 'bytes' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'requiredSignatures', + inputs: [], + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}, { + name: 'numMessagesSigned', + inputs: [{ type: 'bytes32' }], // messageHash (TODO: double check) + outputs: [{ type: 'uint256' }], + stateMutability: 'view', + type: 'function' +}] diff --git a/src/rest/DataUnionEndpoints.ts b/src/rest/DataUnionEndpoints.ts deleted file mode 100644 index e78c1a55a..000000000 --- a/src/rest/DataUnionEndpoints.ts +++ /dev/null @@ -1,1110 +0,0 @@ -/** - * Streamr Data Union related functions - * - * Table of Contents: - * ABIs - * helper utils - * admin: DEPLOY AND SETUP DATA UNION Functions for deploying the contract and adding secrets for smooth joining - * admin: MANAGE DATA UNION add and part members - * member: JOIN & QUERY DATA UNION Publicly available info about dataunions and their members (with earnings and proofs) - * member: WITHDRAW EARNINGS Withdrawing functions, there's many: normal, agent, donate - */ - -import { getAddress, getCreate2Address, isAddress } from '@ethersproject/address' -import { BigNumber } from '@ethersproject/bignumber' -import { arrayify, hexZeroPad } from '@ethersproject/bytes' -import { Contract } from '@ethersproject/contracts' -import { keccak256 } from '@ethersproject/keccak256' -import { defaultAbiCoder } from '@ethersproject/abi' -import { TransactionReceipt, TransactionResponse } from '@ethersproject/providers' -import { verifyMessage } from '@ethersproject/wallet' -import debug from 'debug' -import { DataUnionDeployOptions, DataUnionMemberListModificationOptions, DataUnionWithdrawOptions } from '../dataunion/DataUnion' -import StreamrClient from '../StreamrClient' -import { Todo } from '../types' - -import { until, getEndpointUrl } from '../utils' - -import authFetch from './authFetch' - -export interface DataUnionStats { - activeMemberCount: Todo, - inactiveMemberCount: Todo, - joinPartAgentCount: Todo, - totalEarnings: Todo, - totalWithdrawable: Todo, - lifetimeMemberEarnings: Todo -} - -export interface MemberStats { - status: Todo - earningsBeforeLastJoin: Todo - lmeAtJoin: Todo - totalEarnings: Todo - withdrawableEarnings: Todo -} - -const log = debug('StreamrClient::DataUnionEndpoints') -// const log = console.log // useful for debugging sometimes - -// /////////////////////////////////////////////////////////////////////// -// ABIs: contract functions we want to call within the client -// /////////////////////////////////////////////////////////////////////// - -const dataUnionMainnetABI = [{ - name: 'sendTokensToBridge', - inputs: [], - outputs: [{ type: 'uint256' }], - stateMutability: 'nonpayable', - type: 'function' -}, { - name: 'token', - inputs: [], - outputs: [{ type: 'address' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'owner', - inputs: [], - outputs: [{ type: 'address' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'setAdminFee', - inputs: [{ type: 'uint256' }], - outputs: [], - stateMutability: 'nonpayable', - type: 'function' -}, { - name: 'adminFeeFraction', - inputs: [], - outputs: [{ type: 'uint256' }], - stateMutability: 'view', - type: 'function' -}] - -const dataUnionSidechainABI = [{ - name: 'addMembers', - inputs: [{ type: 'address[]', internalType: 'address payable[]', }], - outputs: [], - stateMutability: 'nonpayable', - type: 'function' -}, { - name: 'partMembers', - inputs: [{ type: 'address[]' }], - outputs: [], - stateMutability: 'nonpayable', - type: 'function' -}, { - name: 'withdrawAll', - inputs: [{ type: 'address' }, { type: 'bool' }], - outputs: [{ type: 'uint256' }], - stateMutability: 'nonpayable', - type: 'function' -}, { - name: 'withdrawAllTo', - inputs: [{ type: 'address' }, { type: 'bool' }], - outputs: [{ type: 'uint256' }], - stateMutability: 'nonpayable', - type: 'function' -}, { - name: 'withdrawAllToSigned', - inputs: [{ type: 'address' }, { type: 'address' }, { type: 'bool' }, { type: 'bytes' }], - outputs: [{ type: 'uint256' }], - stateMutability: 'nonpayable', - type: 'function' -}, { - // enum ActiveStatus {None, Active, Inactive, Blocked} - // struct MemberInfo { - // ActiveStatus status; - // uint256 earnings_before_last_join; - // uint256 lme_at_join; - // uint256 withdrawnEarnings; - // } - name: 'memberData', - inputs: [{ type: 'address' }], - outputs: [{ type: 'uint8' }, { type: 'uint256' }, { type: 'uint256' }, { type: 'uint256' }], - stateMutability: 'view', - type: 'function' -}, { - inputs: [], - name: 'getStats', - outputs: [{ type: 'uint256[6]' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'getEarnings', - inputs: [{ type: 'address' }], - outputs: [{ type: 'uint256' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'getWithdrawableEarnings', - inputs: [{ type: 'address' }], - outputs: [{ type: 'uint256' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'lifetimeMemberEarnings', - inputs: [], - outputs: [{ type: 'uint256' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'totalWithdrawable', - inputs: [], - outputs: [{ type: 'uint256' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'totalEarnings', - inputs: [], - outputs: [{ type: 'uint256' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'activeMemberCount', - inputs: [], - outputs: [{ type: 'uint256' }], - stateMutability: 'view', - type: 'function' -}, { - // this event is emitted by withdrawing process, - // see https://github.com/poanetwork/tokenbridge-contracts/blob/master/contracts/upgradeable_contracts/arbitrary_message/HomeAMB.sol - name: 'UserRequestForSignature', - inputs: [ - { indexed: true, name: 'messageId', type: 'bytes32' }, - { indexed: false, name: 'encodedData', type: 'bytes' } - ], - anonymous: false, - type: 'event' -}] - -// Only the part of ABI that is needed by deployment (and address resolution) -const factoryMainnetABI = [{ - type: 'constructor', - inputs: [{ type: 'address' }, { type: 'address' }, { type: 'address' }, { type: 'address' }, { type: 'uint256' }], - stateMutability: 'nonpayable' -}, { - name: 'sidechainAddress', - inputs: [{ type: 'address' }], - outputs: [{ type: 'address' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'mainnetAddress', - inputs: [{ type: 'address' }, { type: 'string' }], - outputs: [{ type: 'address' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'deployNewDataUnion', - inputs: [{ type: 'address' }, { type: 'uint256' }, { type: 'address[]' }, { type: 'string' }], - outputs: [{ type: 'address' }], - stateMutability: 'nonpayable', - type: 'function' -}, { - name: 'amb', - inputs: [], - outputs: [{ type: 'address' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'data_union_sidechain_factory', - inputs: [], - outputs: [{ type: 'address' }], - stateMutability: 'view', - type: 'function' -}] - -const mainnetAmbABI = [{ - name: 'executeSignatures', - inputs: [{ type: 'bytes' }, { type: 'bytes' }], // data, signatures - outputs: [], - stateMutability: 'nonpayable', - type: 'function' -}, { - name: 'messageCallStatus', - inputs: [{ type: 'bytes32' }], // messageId - outputs: [{ type: 'bool' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'failedMessageSender', - inputs: [{ type: 'bytes32' }], // messageId - outputs: [{ type: 'address' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'relayedMessages', - inputs: [{ type: 'bytes32' }], // messageId, was called "_txhash" though?! - outputs: [{ name: '', type: 'bool' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'validatorContract', - inputs: [], - outputs: [{ type: 'address' }], - stateMutability: 'view', - type: 'function' -}] - -const sidechainAmbABI = [{ - name: 'signature', - inputs: [{ type: 'bytes32' }, { type: 'uint256' }], // messageHash, index - outputs: [{ type: 'bytes' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'message', - inputs: [{ type: 'bytes32' }], // messageHash - outputs: [{ type: 'bytes' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'requiredSignatures', - inputs: [], - outputs: [{ type: 'uint256' }], - stateMutability: 'view', - type: 'function' -}, { - name: 'numMessagesSigned', - inputs: [{ type: 'bytes32' }], // messageHash (TODO: double check) - outputs: [{ type: 'uint256' }], - stateMutability: 'view', - type: 'function' -}] - -// ////////////////////////////////////////////////////////////////// -// Contract utils -// ////////////////////////////////////////////////////////////////// - -type EthereumAddress = string - -function throwIfBadAddress(address: string, variableDescription: Todo) { - try { - return getAddress(address) - } catch (e) { - throw new Error(`${variableDescription || 'Error'}: Bad Ethereum address ${address}. Original error: ${e.stack}.`) - } -} - -// Find the Asyncronous Message-passing Bridge sidechain ("home") contract -let cachedSidechainAmb: Todo -async function getSidechainAmb(client: StreamrClient) { - if (!cachedSidechainAmb) { - const getAmbPromise = async () => { - const mainnetProvider = client.ethereum.getMainnetProvider() - const { factoryMainnetAddress } = client.options - const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, mainnetProvider) - const sidechainProvider = client.ethereum.getSidechainProvider() - const factorySidechainAddress = await factoryMainnet.data_union_sidechain_factory() // TODO use getDataUnionSidechainAddress() - const factorySidechain = new Contract(factorySidechainAddress, [{ - name: 'amb', - inputs: [], - outputs: [{ type: 'address' }], - stateMutability: 'view', - type: 'function' - }], sidechainProvider) - const sidechainAmbAddress = await factorySidechain.amb() - return new Contract(sidechainAmbAddress, sidechainAmbABI, sidechainProvider) - } - cachedSidechainAmb = getAmbPromise() - cachedSidechainAmb = await cachedSidechainAmb // eslint-disable-line require-atomic-updates - } - return cachedSidechainAmb -} - -async function getMainnetAmb(client: StreamrClient) { - const mainnetProvider = client.ethereum.getMainnetProvider() - const { factoryMainnetAddress } = client.options - const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, mainnetProvider) - const mainnetAmbAddress = await factoryMainnet.amb() - return new Contract(mainnetAmbAddress, mainnetAmbABI, mainnetProvider) -} - -async function requiredSignaturesHaveBeenCollected(client: StreamrClient, messageHash: Todo) { - const sidechainAmb = await getSidechainAmb(client) - const requiredSignatureCount = await sidechainAmb.requiredSignatures() - - // Bit 255 is set to mark completion, double check though - const sigCountStruct = await sidechainAmb.numMessagesSigned(messageHash) - const collectedSignatureCount = sigCountStruct.mask(255) - const markedComplete = sigCountStruct.shr(255).gt(0) - - log(`${collectedSignatureCount.toString()} out of ${requiredSignatureCount.toString()} collected`) - if (markedComplete) { log('All signatures collected') } - return markedComplete -} - -// move signatures from sidechain to mainnet -async function transportSignatures(client: StreamrClient, messageHash: string) { - const sidechainAmb = await getSidechainAmb(client) - const message = await sidechainAmb.message(messageHash) - const messageId = '0x' + message.substr(2, 64) - const sigCountStruct = await sidechainAmb.numMessagesSigned(messageHash) - const collectedSignatureCount = sigCountStruct.mask(255).toNumber() - - log(`${collectedSignatureCount} signatures reported, getting them from the sidechain AMB...`) - const signatures = await Promise.all(Array(collectedSignatureCount).fill(0).map(async (_, i) => sidechainAmb.signature(messageHash, i))) - - const [vArray, rArray, sArray]: Todo = [[], [], []] - signatures.forEach((signature: string, i) => { - log(` Signature ${i}: ${signature} (len=${signature.length}=${signature.length / 2 - 1} bytes)`) - rArray.push(signature.substr(2, 64)) - sArray.push(signature.substr(66, 64)) - vArray.push(signature.substr(130, 2)) - }) - const packedSignatures = BigNumber.from(signatures.length).toHexString() + vArray.join('') + rArray.join('') + sArray.join('') - log(`All signatures packed into one: ${packedSignatures}`) - - // Gas estimation also checks that the transaction would succeed, and provides a helpful error message in case it would fail - const mainnetAmb = await getMainnetAmb(client) - log(`Estimating gas using mainnet AMB @ ${mainnetAmb.address}, message=${message}`) - let gasLimit - try { - // magic number suggested by https://github.com/poanetwork/tokenbridge/blob/master/oracle/src/utils/constants.js - gasLimit = BigNumber.from(await mainnetAmb.estimateGas.executeSignatures(message, packedSignatures)).add(200000) - log(`Calculated gas limit: ${gasLimit.toString()}`) - } catch (e) { - // Failure modes from https://github.com/poanetwork/tokenbridge/blob/master/oracle/src/events/processAMBCollectedSignatures/estimateGas.js - log('Gas estimation failed: Check if the message was already processed') - const alreadyProcessed = await mainnetAmb.relayedMessages(messageId) - if (alreadyProcessed) { - log(`WARNING: Tried to transport signatures but they have already been transported (Message ${messageId} has already been processed)`) - log('This could happen if payForSignatureTransport=true, but bridge operator also pays for signatures, and got there before your client') - return null - } - - log('Gas estimation failed: Check if number of signatures is enough') - const mainnetProvider = client.ethereum.getMainnetProvider() - const validatorContractAddress = await mainnetAmb.validatorContract() - const validatorContract = new Contract(validatorContractAddress, [{ - name: 'isValidator', - inputs: [{ type: 'address' }], - outputs: [{ type: 'bool' }], - stateMutability: 'view', - type: 'function' - }, { - name: 'requiredSignatures', - inputs: [], - outputs: [{ type: 'uint256' }], - stateMutability: 'view', - type: 'function' - }], mainnetProvider) - const requiredSignatures = await validatorContract.requiredSignatures() - if (requiredSignatures.gt(signatures.length)) { - throw new Error('The number of required signatures does not match between sidechain(' - + signatures.length + ' and mainnet( ' + requiredSignatures.toString()) - } - - log('Gas estimation failed: Check if all the signatures were made by validators') - log(` Recover signer addresses from signatures [${signatures.join(', ')}]`) - const signers = signatures.map((signature) => verifyMessage(arrayify(message), signature)) - log(` Check that signers are validators [[${signers.join(', ')}]]`) - const isValidatorArray = await Promise.all(signers.map((address) => [address, validatorContract.isValidator(address)])) - const nonValidatorSigners = isValidatorArray.filter(([, isValidator]) => !isValidator) - if (nonValidatorSigners.length > 0) { - throw new Error(`Following signers are not listed as validators in mainnet validator contract at ${validatorContractAddress}:\n - ` - + nonValidatorSigners.map(([address]) => address).join('\n - ')) - } - - throw new Error(`Gas estimation failed: Unknown error while processing message ${message} with ${e.stack}`) - } - - const signer = client.ethereum.getSigner() - log(`Sending message from signer=${await signer.getAddress()}`) - const txAMB = await mainnetAmb.connect(signer).executeSignatures(message, packedSignatures) - const trAMB = await txAMB.wait() - return trAMB -} - -// template for withdraw functions -// client could be replaced with AMB (mainnet and sidechain) -async function untilWithdrawIsComplete( - client: StreamrClient, - getWithdrawTxFunc: () => Promise, - getBalanceFunc: () => Promise, - options: DataUnionWithdrawOptions = {} -) { - const { - pollingIntervalMs = 1000, - retryTimeoutMs = 60000, - }: Todo = options - const balanceBefore = await getBalanceFunc() - const tx = await getWithdrawTxFunc() - const tr = await tx.wait() - - if (options.payForSignatureTransport || client.options.payForSignatureTransport) { - log(`Got receipt, filtering UserRequestForSignature from ${tr.events.length} events...`) - // event UserRequestForSignature(bytes32 indexed messageId, bytes encodedData); - const sigEventArgsArray = tr.events.filter((e: Todo) => e.event === 'UserRequestForSignature').map((e: Todo) => e.args) - if (sigEventArgsArray.length < 1) { - throw new Error("No UserRequestForSignature events emitted from withdraw transaction, can't transport withdraw to mainnet") - } - /* eslint-disable no-await-in-loop */ - // eslint-disable-next-line no-restricted-syntax - for (const eventArgs of sigEventArgsArray) { - const messageId = eventArgs[0] - const messageHash = keccak256(eventArgs[1]) - - log(`Waiting until sidechain AMB has collected required signatures for hash=${messageHash}...`) - await until(async () => requiredSignaturesHaveBeenCollected(client, messageHash), pollingIntervalMs, retryTimeoutMs) - - log(`Checking mainnet AMB hasn't already processed messageId=${messageId}`) - const mainnetAmb = await getMainnetAmb(client) - const alreadySent = await mainnetAmb.messageCallStatus(messageId) - const failAddress = await mainnetAmb.failedMessageSender(messageId) - if (alreadySent || failAddress !== '0x0000000000000000000000000000000000000000') { // zero address means no failed messages - log(`WARNING: Mainnet bridge has already processed withdraw messageId=${messageId}`) - log([ - 'This could happen if payForSignatureTransport=true, but bridge operator also pays for', - 'signatures, and got there before your client', - ].join(' ')) - continue - } - - log(`Transporting signatures for hash=${messageHash}`) - await transportSignatures(client, messageHash) - } - /* eslint-enable no-await-in-loop */ - } - - log(`Waiting for balance ${balanceBefore.toString()} to change`) - await until(async () => !(await getBalanceFunc()).eq(balanceBefore), retryTimeoutMs, pollingIntervalMs) - - return tr -} - -// TODO remove caching as we calculate the values only when deploying the DU -const mainnetAddressCache: Todo = {} // mapping: "name" -> mainnet address - -/** @returns Mainnet address for Data Union */ -async function fetchDataUnionMainnetAddress( - client: StreamrClient, - dataUnionName: string, - deployerAddress: EthereumAddress -): Promise { - if (!mainnetAddressCache[dataUnionName]) { - const provider = client.ethereum.getMainnetProvider() - const { factoryMainnetAddress } = client.options - const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, provider) - const addressPromise = factoryMainnet.mainnetAddress(deployerAddress, dataUnionName) - mainnetAddressCache[dataUnionName] = addressPromise - mainnetAddressCache[dataUnionName] = await addressPromise // eslint-disable-line require-atomic-updates - } - return mainnetAddressCache[dataUnionName] -} - -function getDataUnionMainnetAddress(client: StreamrClient, dataUnionName: string, deployerAddress: EthereumAddress) { - const { factoryMainnetAddress } = client.options - if (!factoryMainnetAddress) { - throw new Error('StreamrClient has no factoryMainnetAddress configuration.') - } - // NOTE! this must be updated when DU sidechain smartcontract changes: keccak256(CloneLib.cloneBytecode(data_union_mainnet_template)); - const codeHash = '0x50a78bac973bdccfc8415d7d9cfd62898b8f7cf6e9b3a15e7d75c0cb820529eb' - const salt = keccak256(defaultAbiCoder.encode(['string', 'address'], [dataUnionName, deployerAddress])) - return getCreate2Address(factoryMainnetAddress, salt, codeHash) -} - -// TODO remove caching as we calculate the values only when deploying the DU -const sidechainAddressCache: Todo = {} // mapping: mainnet address -> sidechain address -/** @returns Sidechain address for Data Union */ -async function fetchDataUnionSidechainAddress(client: StreamrClient, duMainnetAddress: EthereumAddress): Promise { - if (!sidechainAddressCache[duMainnetAddress]) { - const provider = client.ethereum.getMainnetProvider() - const { factoryMainnetAddress } = client.options - const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, provider) - const addressPromise = factoryMainnet.sidechainAddress(duMainnetAddress) - sidechainAddressCache[duMainnetAddress] = addressPromise - sidechainAddressCache[duMainnetAddress] = await addressPromise // eslint-disable-line require-atomic-updates - } - return sidechainAddressCache[duMainnetAddress] -} - -function getDataUnionSidechainAddress(client: StreamrClient, mainnetAddress: EthereumAddress) { - const { factorySidechainAddress } = client.options - if (!factorySidechainAddress) { - throw new Error('StreamrClient has no factorySidechainAddress configuration.') - } - // NOTE! this must be updated when DU sidechain smartcontract changes: keccak256(CloneLib.cloneBytecode(data_union_sidechain_template)) - const codeHash = '0x040cf686e25c97f74a23a4bf01c29dd77e260c4b694f5611017ce9713f58de83' - return getCreate2Address(factorySidechainAddress, hexZeroPad(mainnetAddress, 32), codeHash) -} - -function getMainnetContractReadOnly(contractAddress: EthereumAddress, client: StreamrClient) { - if (isAddress(contractAddress)) { - const provider = client.ethereum.getMainnetProvider() - return new Contract(contractAddress, dataUnionMainnetABI, provider) - } - throw new Error(`${contractAddress} was not a good Ethereum address`) - -} - -function getMainnetContract(contractAddress: EthereumAddress, client: StreamrClient) { - const du = getMainnetContractReadOnly(contractAddress, client) - const signer = client.ethereum.getSigner() - return du.connect(signer) -} - -async function getSidechainContract(contractAddress: EthereumAddress, client: StreamrClient) { - const signer = await client.ethereum.getSidechainSigner() - const duMainnet = getMainnetContractReadOnly(contractAddress, client) - const duSidechainAddress = getDataUnionSidechainAddress(client, duMainnet.address) - const duSidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, signer) - return duSidechain -} - -async function getSidechainContractReadOnly(contractAddress: EthereumAddress, client: StreamrClient) { - const provider = client.ethereum.getSidechainProvider() - const duMainnet = getMainnetContractReadOnly(contractAddress, client) - const duSidechainAddress = getDataUnionSidechainAddress(client, duMainnet.address) - const duSidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, provider) - return duSidechain -} - -export class DataUnionEndpoints { - - client: StreamrClient - - constructor(client: StreamrClient) { - this.client = client - } - - // ////////////////////////////////////////////////////////////////// - // admin: DEPLOY AND SETUP DATA UNION - // ////////////////////////////////////////////////////////////////// - - // TODO inline this function? - calculateDataUnionMainnetAddress(dataUnionName: string, deployerAddress: EthereumAddress) { - const address = getAddress(deployerAddress) // throws if bad address - return getDataUnionMainnetAddress(this.client, dataUnionName, address) - } - - // TODO inline this function? - calculateDataUnionSidechainAddress(duMainnetAddress: EthereumAddress) { - const address = getAddress(duMainnetAddress) // throws if bad address - return getDataUnionSidechainAddress(this.client, address) - } - - /** - * Create a new DataUnionMainnet contract to mainnet with DataUnionFactoryMainnet - * This triggers DataUnionSidechain contract creation in sidechain, over the bridge (AMB) - * @return that resolves when the new DU is deployed over the bridge to side-chain - */ - async deployDataUnionContract(options: DataUnionDeployOptions = {}): Promise { - const { - owner, - joinPartAgents, - dataUnionName, - adminFee = 0, - sidechainPollingIntervalMs = 1000, - sidechainRetryTimeoutMs = 600000, - confirmations = 1, - gasPrice - } = options - - let duName = dataUnionName - if (!duName) { - duName = `DataUnion-${Date.now()}` // TODO: use uuid - log(`dataUnionName generated: ${duName}`) - } - - if (adminFee < 0 || adminFee > 1) { throw new Error('options.adminFeeFraction must be a number between 0...1, got: ' + adminFee) } - const adminFeeBN = BigNumber.from((adminFee * 1e18).toFixed()) // last 2...3 decimals are going to be gibberish - - const mainnetProvider = this.client.ethereum.getMainnetProvider() - const mainnetWallet = this.client.ethereum.getSigner() - const sidechainProvider = this.client.ethereum.getSidechainProvider() - - const ownerAddress = (owner) ? getAddress(owner) : this.client.getAddress() - - let agentAddressList - if (Array.isArray(joinPartAgents)) { - // getAddress throws if there's an invalid address in the array - agentAddressList = joinPartAgents.map(getAddress) - } else { - // streamrNode needs to be joinPartAgent so that EE join with secret works (and join approvals from Marketplace UI) - agentAddressList = [ownerAddress] - if (this.client.options.streamrNodeAddress) { - agentAddressList.push(getAddress(this.client.options.streamrNodeAddress)) - } - } - - const deployerAddress = this.client.getAddress() - const duMainnetAddress = await fetchDataUnionMainnetAddress(this.client, duName, deployerAddress) - const duSidechainAddress = await fetchDataUnionSidechainAddress(this.client, duMainnetAddress) - - if (await mainnetProvider.getCode(duMainnetAddress) !== '0x') { - throw new Error(`Mainnet data union "${duName}" contract ${duMainnetAddress} already exists!`) - } - - const factoryMainnetAddress = throwIfBadAddress( - this.client.options.factoryMainnetAddress!, - 'StreamrClient.options.factoryMainnetAddress' - ) - if (await mainnetProvider.getCode(factoryMainnetAddress) === '0x') { - throw new Error(`Data union factory contract not found at ${factoryMainnetAddress}, check StreamrClient.options.factoryMainnetAddress!`) - } - - // function deployNewDataUnion(address owner, uint256 adminFeeFraction, address[] agents, string duName) - const factoryMainnet = new Contract(factoryMainnetAddress!, factoryMainnetABI, mainnetWallet) - const ethersOptions: any = {} - if (gasPrice) { - ethersOptions.gasPrice = gasPrice - } - const tx = await factoryMainnet.deployNewDataUnion( - ownerAddress, - adminFeeBN, - agentAddressList, - duName, - ethersOptions - ) - const tr = await tx.wait(confirmations) - - log(`Data Union "${duName}" (mainnet: ${duMainnetAddress}, sidechain: ${duSidechainAddress}) deployed to mainnet, waiting for side-chain...`) - await until( - async () => await sidechainProvider.getCode(duSidechainAddress) !== '0x', - sidechainRetryTimeoutMs, - sidechainPollingIntervalMs - ) - - const dataUnion = new Contract(duMainnetAddress, dataUnionMainnetABI, mainnetWallet) - // @ts-expect-error - dataUnion.deployTxReceipt = tr - // @ts-expect-error - dataUnion.sidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, sidechainProvider) - return dataUnion - } - - async getContract(contractAddress: EthereumAddress) { - const ret = getMainnetContract(contractAddress, this.client) - // @ts-expect-error - ret.sidechain = await getSidechainContract(contractAddress, this.client) - return ret - } - - /** - * Add a new data union secret - */ - async createSecret(dataUnionMainnetAddress: EthereumAddress, name: string = 'Untitled Data Union Secret'): Promise { - const duAddress = getAddress(dataUnionMainnetAddress) // throws if bad address - const url = getEndpointUrl(this.client.options.restUrl, 'dataunions', duAddress, 'secrets') - const res = await authFetch( - url, - this.client.session, - { - method: 'POST', - body: JSON.stringify({ - name - }), - headers: { - 'Content-Type': 'application/json', - }, - }, - ) - return res.secret - } - - // ////////////////////////////////////////////////////////////////// - // admin: MANAGE DATA UNION - // ////////////////////////////////////////////////////////////////// - - /** - * Add given Ethereum addresses as data union members - */ - async addMembers( - memberAddressList: string[], - options: DataUnionMemberListModificationOptions|undefined = {}, - contractAddress: EthereumAddress - ): Promise { - const members = memberAddressList.map(getAddress) // throws if there are bad addresses - const duSidechain = await getSidechainContract(contractAddress, this.client) - const tx = await duSidechain.addMembers(members) - // TODO: wrap promise for better error reporting in case tx fails (parse reason, throw proper error) - const { confirmations = 1 } = options - return tx.wait(confirmations) - } - - /** - * Remove given members from data union - */ - async removeMembers( - memberAddressList: string[], - options: DataUnionMemberListModificationOptions|undefined = {}, - contractAddress: EthereumAddress - ): Promise { - const members = memberAddressList.map(getAddress) // throws if there are bad addresses - const duSidechain = await getSidechainContract(contractAddress, this.client) - const tx = await duSidechain.partMembers(members) - // TODO: wrap promise for better error reporting in case tx fails (parse reason, throw proper error) - const { confirmations = 1 } = options - return tx.wait(confirmations) - } - - /** - * Admin: withdraw earnings (pay gas) on behalf of a member - * TODO: add test - * @param memberAddress - the other member who gets their tokens out of the Data Union - * @returns Receipt once withdraw transaction is confirmed - */ - async withdrawAllToMember( - memberAddress: EthereumAddress, - options: DataUnionWithdrawOptions|undefined, - contractAddress: EthereumAddress - ): Promise { - const address = getAddress(memberAddress) // throws if bad address - const tr = await untilWithdrawIsComplete( - this.client, - () => this.getWithdrawAllToMemberTx(address, contractAddress), - () => this.getTokenBalance(address), - options - ) - return tr - } - - /** - * Admin: get the tx promise for withdrawing all earnings on behalf of a member - * @param memberAddress - the other member who gets their tokens out of the Data Union - * @returns await on call .wait to actually send the tx - */ - async getWithdrawAllToMemberTx(memberAddress: EthereumAddress, contractAddress: EthereumAddress): Promise { - const a = getAddress(memberAddress) // throws if bad address - const duSidechain = await getSidechainContract(contractAddress, this.client) - return duSidechain.withdrawAll(a, true) // sendToMainnet=true - } - - /** - * Admin: Withdraw a member's earnings to another address, signed by the member - * @param memberAddress - the member whose earnings are sent out - * @param recipientAddress - the address to receive the tokens in mainnet - * @param signature - from member, produced using signWithdrawAllTo - * @returns receipt once withdraw transaction is confirmed - */ - async withdrawAllToSigned( - memberAddress: EthereumAddress, - recipientAddress: EthereumAddress, - signature: string, - options: DataUnionWithdrawOptions|undefined, - contractAddress: EthereumAddress - ): Promise { - const from = getAddress(memberAddress) // throws if bad address - const to = getAddress(recipientAddress) - const tr = await untilWithdrawIsComplete( - this.client, - () => this.getWithdrawAllToSignedTx(from, to, signature, contractAddress), - () => this.getTokenBalance(to), - options - ) - return tr - } - - /** - * Admin: Withdraw a member's earnings to another address, signed by the member - * @param memberAddress - the member whose earnings are sent out - * @param recipientAddress - the address to receive the tokens in mainnet - * @param signature - from member, produced using signWithdrawAllTo - * @returns await on call .wait to actually send the tx - */ - async getWithdrawAllToSignedTx( - memberAddress: EthereumAddress, - recipientAddress: EthereumAddress, - signature: string, - contractAddress: EthereumAddress - ): Promise { - const duSidechain = await getSidechainContract(contractAddress, this.client) - return duSidechain.withdrawAllToSigned(memberAddress, recipientAddress, true, signature) // sendToMainnet=true - } - - /** - * Admin: set admin fee (between 0.0 and 1.0) for the data union - */ - async setAdminFee(newFeeFraction: number, contractAddress: EthereumAddress): Promise { - if (newFeeFraction < 0 || newFeeFraction > 1) { - throw new Error('newFeeFraction argument must be a number between 0...1, got: ' + newFeeFraction) - } - const adminFeeBN = BigNumber.from((newFeeFraction * 1e18).toFixed()) // last 2...3 decimals are going to be gibberish - const duMainnet = getMainnetContract(contractAddress, this.client) - const tx = await duMainnet.setAdminFee(adminFeeBN) - return tx.wait() - } - - /** - * Get data union admin fee fraction (between 0.0 and 1.0) that admin gets from each revenue event - */ - async getAdminFee(contractAddress: EthereumAddress): Promise { - const duMainnet = getMainnetContractReadOnly(contractAddress, this.client) - const adminFeeBN = await duMainnet.adminFeeFraction() - return +adminFeeBN.toString() / 1e18 - } - - async getAdminAddress(contractAddress: EthereumAddress): Promise { - const duMainnet = getMainnetContractReadOnly(contractAddress, this.client) - return duMainnet.owner() - } - - // ////////////////////////////////////////////////////////////////// - // member: JOIN & QUERY DATA UNION - // ////////////////////////////////////////////////////////////////// - - /** - * Send a joinRequest, or get into data union instantly with a data union secret - */ - async join(secret: string|undefined, contractAddress: EthereumAddress): Promise { - const memberAddress = this.client.getAddress() as string - const body: any = { - memberAddress - } - if (secret) { body.secret = secret } - - const url = getEndpointUrl(this.client.options.restUrl, 'dataunions', contractAddress, 'joinRequests') - const response = await authFetch( - url, - this.client.session, - { - method: 'POST', - body: JSON.stringify(body), - headers: { - 'Content-Type': 'application/json', - }, - }, - ) - if (secret) { - await until(async () => this.isMember(memberAddress, contractAddress)) - } - return response - } - - async isMember(memberAddress: EthereumAddress, contractAddress: EthereumAddress): Promise { - const address = getAddress(memberAddress) - const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) - const ACTIVE = 1 // memberData[0] is enum ActiveStatus {None, Active, Inactive} - const memberData = await duSidechain.memberData(address) - const state = memberData[0] - return (state === ACTIVE) - } - - // TODO: this needs more thought: probably something like getEvents from sidechain? Heavy on RPC? - async getMembers(contractAddress: EthereumAddress) { - const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) - throw new Error(`Not implemented for side-chain data union (at ${duSidechain.address})`) - // event MemberJoined(address indexed); - // event MemberParted(address indexed); - } - - async getStats(contractAddress: EthereumAddress): Promise { - const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) - const [ - totalEarnings, - totalEarningsWithdrawn, - activeMemberCount, - inactiveMemberCount, - lifetimeMemberEarnings, - joinPartAgentCount, - ] = await duSidechain.getStats() - const totalWithdrawable = totalEarnings.sub(totalEarningsWithdrawn) - return { - activeMemberCount, - inactiveMemberCount, - joinPartAgentCount, - totalEarnings, - totalWithdrawable, - lifetimeMemberEarnings, - } - } - - /** - * Get stats of a single data union member - */ - async getMemberStats(memberAddress: EthereumAddress, contractAddress: EthereumAddress): Promise { - const address = getAddress(memberAddress) - // TODO: use duSidechain.getMemberStats(address) once it's implemented, to ensure atomic read - // (so that memberData is from same block as getEarnings, otherwise withdrawable will be foobar) - const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) - const mdata = await duSidechain.memberData(address) - const total = await duSidechain.getEarnings(address).catch(() => 0) - const withdrawnEarnings = mdata[3].toString() - const withdrawable = total ? total.sub(withdrawnEarnings) : 0 - return { - status: ['unknown', 'active', 'inactive', 'blocked'][mdata[0]], - earningsBeforeLastJoin: mdata[1].toString(), - lmeAtJoin: mdata[2].toString(), - totalEarnings: total.toString(), - withdrawableEarnings: withdrawable.toString(), - } - } - - /** - * Get the amount of tokens the member would get from a successful withdraw - */ - async getWithdrawableEarnings(memberAddress: EthereumAddress, contractAddress: EthereumAddress): Promise { - const address = getAddress(memberAddress) - const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) - return duSidechain.getWithdrawableEarnings(address) - } - - /** - * Get token balance in "wei" (10^-18 parts) for given address - */ - async getTokenBalance(address: string): Promise { - const { tokenAddress } = this.client.options - if (!tokenAddress) { - throw new Error('StreamrClient has no tokenAddress configuration.') - } - const addr = getAddress(address) - const provider = this.client.ethereum.getMainnetProvider() - - const token = new Contract(tokenAddress, [{ - name: 'balanceOf', - inputs: [{ type: 'address' }], - outputs: [{ type: 'uint256' }], - constant: true, - payable: false, - stateMutability: 'view', - type: 'function' - }], provider) - return token.balanceOf(addr) - } - - /** - * Figure out if given mainnet address is old DataUnion (v 1.0) or current 2.0 - * NOTE: Current version of streamr-client-javascript can only handle current version! - */ - async getVersion(contractAddress: EthereumAddress): Promise { - const a = getAddress(contractAddress) // throws if bad address - const provider = this.client.ethereum.getMainnetProvider() - const du = new Contract(a, [{ - name: 'version', - inputs: [], - outputs: [{ type: 'uint256' }], - stateMutability: 'view', - type: 'function' - }], provider) - try { - const version = await du.version() - return +version - } catch (e) { - // "not a data union" - return 0 - } - } - - // ////////////////////////////////////////////////////////////////// - // member: WITHDRAW EARNINGS - // ////////////////////////////////////////////////////////////////// - - /** - * Withdraw all your earnings - * @returns receipt once withdraw is complete (tokens are seen in mainnet) - */ - async withdrawAll(contractAddress: EthereumAddress, options?: DataUnionWithdrawOptions): Promise { - const recipientAddress = this.client.getAddress() - const tr = await untilWithdrawIsComplete( - this.client, - () => this.getWithdrawAllTx(contractAddress), - () => this.getTokenBalance(recipientAddress), - options - ) - return tr - } - - /** - * Get the tx promise for withdrawing all your earnings - * @returns await on call .wait to actually send the tx - */ - async getWithdrawAllTx(contractAddress: EthereumAddress): Promise { - const signer = await this.client.ethereum.getSidechainSigner() - const address = await signer.getAddress() - const duSidechain = await getSidechainContract(contractAddress, this.client) - - const withdrawable = await duSidechain.getWithdrawableEarnings(address) - if (withdrawable.eq(0)) { - throw new Error(`${address} has nothing to withdraw in (sidechain) data union ${duSidechain.address}`) - } - - if (this.client.options.minimumWithdrawTokenWei && withdrawable.lt(this.client.options.minimumWithdrawTokenWei)) { - throw new Error(`${address} has only ${withdrawable} to withdraw in ` - + `(sidechain) data union ${duSidechain.address} (min: ${this.client.options.minimumWithdrawTokenWei})`) - } - return duSidechain.withdrawAll(address, true) // sendToMainnet=true - } - - /** - * Withdraw earnings and "donate" them to the given address - * @returns get receipt once withdraw is complete (tokens are seen in mainnet) - */ - async withdrawAllTo( - recipientAddress: EthereumAddress, - options: DataUnionWithdrawOptions|undefined, - contractAddress: EthereumAddress - ): Promise { - const to = getAddress(recipientAddress) // throws if bad address - const tr = await untilWithdrawIsComplete( - this.client, - () => this.getWithdrawAllToTx(to, contractAddress), - () => this.getTokenBalance(to), - options - ) - return tr - } - - /** - * Withdraw earnings and "donate" them to the given address - * @param recipientAddress - the address to receive the tokens - * @returns await on call .wait to actually send the tx - */ - async getWithdrawAllToTx(recipientAddress: EthereumAddress, contractAddress: EthereumAddress): Promise { - const signer = await this.client.ethereum.getSidechainSigner() - const address = await signer.getAddress() - const duSidechain = await getSidechainContract(contractAddress, this.client) - const withdrawable = await duSidechain.getWithdrawableEarnings(address) - if (withdrawable.eq(0)) { - throw new Error(`${address} has nothing to withdraw in (sidechain) data union ${duSidechain.address}`) - } - return duSidechain.withdrawAllTo(recipientAddress, true) // sendToMainnet=true - } - - /** - * Member can sign off to "donate" all earnings to another address such that someone else - * can submit the transaction (and pay for the gas) - * This signature is only valid until next withdrawal takes place (using this signature or otherwise). - * Note that while it's a "blank cheque" for withdrawing all earnings at the moment it's used, it's - * invalidated by the first withdraw after signing it. In other words, any signature can be invalidated - * by making a "normal" withdraw e.g. `await streamrClient.withdrawAll()` - * Admin can execute the withdraw using this signature: ``` - * await adminStreamrClient.withdrawAllToSigned(memberAddress, recipientAddress, signature) - * ``` - * @param recipientAddress - the address authorized to receive the tokens - * @returns signature authorizing withdrawing all earnings to given recipientAddress - */ - async signWithdrawAllTo(recipientAddress: EthereumAddress, contractAddress: EthereumAddress): Promise { - return this.signWithdrawAmountTo(recipientAddress, BigNumber.from(0), contractAddress) - } - - /** - * Member can sign off to "donate" specific amount of earnings to another address such that someone else - * can submit the transaction (and pay for the gas) - * This signature is only valid until next withdrawal takes place (using this signature or otherwise). - * @param recipientAddress - the address authorized to receive the tokens - * @param amountTokenWei - that the signature is for (can't be used for less or for more) - * @returns signature authorizing withdrawing all earnings to given recipientAddress - */ - async signWithdrawAmountTo( - recipientAddress: EthereumAddress, - amountTokenWei: BigNumber|number|string, - contractAddress: EthereumAddress - ): Promise { - const to = getAddress(recipientAddress) // throws if bad address - const signer = this.client.ethereum.getSigner() // it shouldn't matter if it's mainnet or sidechain signer since key should be the same - const address = await signer.getAddress() - const duSidechain = await getSidechainContractReadOnly(contractAddress, this.client) - const memberData = await duSidechain.memberData(address) - if (memberData[0] === '0') { throw new Error(`${address} is not a member in Data Union (sidechain address ${duSidechain.address})`) } - const withdrawn = memberData[3] - // @ts-expect-error - const message = to + hexZeroPad(amountTokenWei, 32).slice(2) + duSidechain.address.slice(2) + hexZeroPad(withdrawn, 32).slice(2) - const signature = await signer.signMessage(arrayify(message)) - return signature - } -} - diff --git a/src/rest/authFetch.js b/src/rest/authFetch.js index 7fbc5b110..7fd8b2297 100644 --- a/src/rest/authFetch.js +++ b/src/rest/authFetch.js @@ -21,7 +21,7 @@ export class AuthFetchError extends Error { } } -const debug = Debug('StreamrClient:utils:authfetch') +const debug = Debug('StreamrClient:utils:authfetch') // TODO: could use the debug instance from the client? (e.g. client.debug.extend('authFetch')) let ID = 0 diff --git a/src/types.ts b/src/types.ts index 3daaca418..739afa588 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,7 @@ import { F } from 'ts-toolbelt' +export type EthereumAddress = string + export type MaybeAsync = T | F.Promisify // Utility Type: make a function maybe async export type Todo = any diff --git a/test/integration/dataunion/DataUnionEndpoints.test.ts b/test/integration/dataunion/DataUnionEndpoints.test.ts deleted file mode 100644 index da5681917..000000000 --- a/test/integration/dataunion/DataUnionEndpoints.test.ts +++ /dev/null @@ -1,219 +0,0 @@ -import { Contract, providers, Wallet } from 'ethers' -import { parseEther, formatEther } from 'ethers/lib/utils' -import { Mutex } from 'async-mutex' -import debug from 'debug' - -import { getEndpointUrl } from '../../../src/utils' -import authFetch from '../../../src/rest/authFetch' -import StreamrClient from '../../../src/StreamrClient' -import * as Token from '../../../contracts/TestToken.json' -import config from '../config' -import { DataUnion } from '../../../src/dataunion/DataUnion' - -const log = debug('StreamrClient::DataUnionEndpoints::integration-test') -// const log = console.log - -// @ts-expect-error -const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) -// @ts-expect-error -const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) -const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) - -describe('DataUnionEndPoints', () => { - let adminClient: StreamrClient - - const tokenAdminWallet = new Wallet(config.tokenAdminPrivateKey, providerMainnet) - const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) - - afterAll(async () => { - providerMainnet.removeAllListeners() - providerSidechain.removeAllListeners() - await adminClient.ensureDisconnected() - }) - - const streamrClientCleanupList: StreamrClient[] = [] - afterAll(async () => Promise.all(streamrClientCleanupList.map((c) => c.ensureDisconnected()))) - - beforeAll(async () => { - log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) - const network = await providerMainnet.getNetwork() - log('Connected to "mainnet" network: ', JSON.stringify(network)) - const network2 = await providerSidechain.getNetwork() - log('Connected to sidechain network: ', JSON.stringify(network2)) - - log(`Minting 100 tokens to ${adminWalletMainnet.address}`) - const tx1 = await tokenMainnet.mint(adminWalletMainnet.address, parseEther('100')) - await tx1.wait() - - adminClient = new StreamrClient(config.clientOptions as any) - await adminClient.ensureConnected() - }, 10000) - - // fresh dataUnion for each test case, created NOT in parallel to avoid nonce troubles - const adminMutex = new Mutex() - async function deployDataUnionSync(testName: string) { - let dataUnion: DataUnion - await adminMutex.runExclusive(async () => { - const dataUnionName = testName + Date.now() - log(`Starting deployment of dataUnionName=${dataUnionName}`) - dataUnion = await adminClient.deployDataUnion({ dataUnionName }) - log(`DataUnion ${dataUnion.getAddress()} is ready to roll`) - - // product is needed for join requests to analyze the DU version - const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') - await authFetch( - createProductUrl, - adminClient.session, - { - method: 'POST', - body: JSON.stringify({ - beneficiaryAddress: dataUnion.getAddress(), - type: 'DATAUNION', - dataUnionVersion: 2 - }) - } - ) - }) - return dataUnion! - } - - describe('Admin', () => { - const memberAddressList = [ - '0x0000000000000000000000000000000000000001', - '0x0000000000000000000000000000000000000002', - '0x000000000000000000000000000000000000bEEF', - ] - - it('can add members', async () => { - const dataUnion = await deployDataUnionSync('add-members-test') - await adminMutex.runExclusive(() => dataUnion.addMembers(memberAddressList)) - const res = await dataUnion.getStats() - expect(+res.activeMemberCount).toEqual(3) - expect(+res.inactiveMemberCount).toEqual(0) - }, 150000) - - it('can remove members', async () => { - const dataUnion = await deployDataUnionSync('remove-members-test') - await adminMutex.runExclusive(async () => { - await dataUnion.addMembers(memberAddressList) - await dataUnion.removeMembers(memberAddressList.slice(1)) - }) - const res = await dataUnion.getStats() - expect(+res.activeMemberCount).toEqual(1) - expect(+res.inactiveMemberCount).toEqual(2) - }, 150000) - - it('can set admin fee', async () => { - const dataUnion = await deployDataUnionSync('set-admin-fee-test') - const oldFee = await dataUnion.getAdminFee() - await adminMutex.runExclusive(async () => { - log(`DU owner: ${await dataUnion.getAdminAddress()}`) - log(`Sending tx from ${adminClient.getAddress()}`) - const tr = await dataUnion.setAdminFee(0.1) - log(`Transaction receipt: ${JSON.stringify(tr)}`) - }) - const newFee = await dataUnion.getAdminFee() - expect(oldFee).toEqual(0) - expect(newFee).toEqual(0.1) - }, 150000) - - it('receives admin fees', async () => { - const dataUnion = await deployDataUnionSync('withdraw-admin-fees-test') - - await adminMutex.runExclusive(async () => { - await dataUnion.addMembers(memberAddressList) - const tr = await dataUnion.setAdminFee(0.1) - log(`Transaction receipt: ${JSON.stringify(tr)}`) - }) - - const amount = parseEther('2') - // eslint-disable-next-line no-underscore-dangle - const contract = await dataUnion._getContract() - const tokenAddress = await contract.token() - const adminTokenMainnet = new Contract(tokenAddress, Token.abi, adminWalletMainnet) - - await adminMutex.runExclusive(async () => { - log(`Transferring ${amount} token-wei ${adminWalletMainnet.address}->${dataUnion.getAddress()}`) - const txTokenToDU = await adminTokenMainnet.transfer(dataUnion.getAddress(), amount) - await txTokenToDU.wait() - }) - - const balance1 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) - log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance1)} (${balance1.toString()})`) - - log(`Transferred ${formatEther(amount)} tokens, next sending to bridge`) - const tx2 = await contract.sendTokensToBridge() - await tx2.wait() - - const balance2 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) - log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance2)} (${balance2.toString()})`) - - expect(formatEther(balance2.sub(balance1))).toEqual('0.2') - }, 150000) - }) - - describe('Anyone', () => { - const nonce = Date.now() - const memberAddressList = [ - `0x100000000000000000000000000${nonce}`, - `0x200000000000000000000000000${nonce}`, - `0x300000000000000000000000000${nonce}`, - ] - - async function getOutsiderClient() { - const client = new StreamrClient({ - ...config.clientOptions, - auth: { - apiKey: 'tester1-api-key' - }, - autoConnect: false, - autoDisconnect: false, - } as any) - streamrClientCleanupList.push(client) - return client - } - - it('can get dataUnion stats', async () => { - const dataUnion = await deployDataUnionSync('get-du-stats-test') - await adminMutex.runExclusive(async () => { - await dataUnion.addMembers(memberAddressList) - }) - const client = await getOutsiderClient() - const stats = await client.getDataUnion(dataUnion.getAddress()).getStats() - expect(+stats.activeMemberCount).toEqual(3) - expect(+stats.inactiveMemberCount).toEqual(0) - expect(+stats.joinPartAgentCount).toEqual(2) - expect(+stats.totalEarnings).toEqual(0) - expect(+stats.totalWithdrawable).toEqual(0) - expect(+stats.lifetimeMemberEarnings).toEqual(0) - }, 150000) - - it('can get member stats', async () => { - const dataUnion = await deployDataUnionSync('get-member-stats-test') - await adminMutex.runExclusive(async () => { - await dataUnion.addMembers(memberAddressList) - }) - const client = await getOutsiderClient() - const memberStats = await Promise.all(memberAddressList.map((m) => client.getDataUnion(dataUnion.getAddress()).getMemberStats(m))) - expect(memberStats).toMatchObject([{ - status: 'active', - earningsBeforeLastJoin: '0', - lmeAtJoin: '0', - totalEarnings: '0', - withdrawableEarnings: '0', - }, { - status: 'active', - earningsBeforeLastJoin: '0', - lmeAtJoin: '0', - totalEarnings: '0', - withdrawableEarnings: '0', - }, { - status: 'active', - earningsBeforeLastJoin: '0', - lmeAtJoin: '0', - totalEarnings: '0', - withdrawableEarnings: '0', - }]) - }, 150000) - }) -}) diff --git a/test/integration/dataunion/adminFee.test.ts b/test/integration/dataunion/adminFee.test.ts new file mode 100644 index 000000000..18a2bd48e --- /dev/null +++ b/test/integration/dataunion/adminFee.test.ts @@ -0,0 +1,80 @@ +import { Contract, providers, Wallet } from 'ethers' +import { parseEther, formatEther } from 'ethers/lib/utils' +import debug from 'debug' + +import StreamrClient from '../../../src/StreamrClient' +import * as Token from '../../../contracts/TestToken.json' +import config from '../config' + +const log = debug('StreamrClient::DataUnion::integration-test-adminFee') + +// @ts-expect-error +const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +// @ts-expect-error +const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) +const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, providerMainnet) + +describe('DataUnion admin fee', () => { + let adminClient: StreamrClient + + const tokenAdminWallet = new Wallet(config.tokenAdminPrivateKey, providerMainnet) + const tokenMainnet = new Contract(config.clientOptions.tokenAddress, Token.abi, tokenAdminWallet) + + beforeAll(async () => { + log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) + const network = await providerMainnet.getNetwork() + log('Connected to "mainnet" network: ', JSON.stringify(network)) + const network2 = await providerSidechain.getNetwork() + log('Connected to sidechain network: ', JSON.stringify(network2)) + log(`Minting 100 tokens to ${adminWalletMainnet.address}`) + const tx1 = await tokenMainnet.mint(adminWalletMainnet.address, parseEther('100')) + await tx1.wait() + adminClient = new StreamrClient(config.clientOptions as any) + }, 10000) + + afterAll(() => { + providerMainnet.removeAllListeners() + providerSidechain.removeAllListeners() + }) + + it('can set admin fee', async () => { + const dataUnion = await adminClient.deployDataUnion() + const oldFee = await dataUnion.getAdminFee() + log(`DU owner: ${await dataUnion.getAdminAddress()}`) + log(`Sending tx from ${adminClient.getAddress()}`) + const tr = await dataUnion.setAdminFee(0.1) + log(`Transaction receipt: ${JSON.stringify(tr)}`) + const newFee = await dataUnion.getAdminFee() + expect(oldFee).toEqual(0) + expect(newFee).toEqual(0.1) + }, 150000) + + it('receives admin fees', async () => { + const dataUnion = await adminClient.deployDataUnion() + const tr = await dataUnion.setAdminFee(0.1) + log(`Transaction receipt: ${JSON.stringify(tr)}`) + + const amount = parseEther('2') + // eslint-disable-next-line no-underscore-dangle + const contract = await dataUnion._getContract() + const tokenAddress = await contract.token() + const adminTokenMainnet = new Contract(tokenAddress, Token.abi, adminWalletMainnet) + + log(`Transferring ${amount} token-wei ${adminWalletMainnet.address}->${dataUnion.getAddress()}`) + const txTokenToDU = await adminTokenMainnet.transfer(dataUnion.getAddress(), amount) + await txTokenToDU.wait() + + const balance1 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance1)} (${balance1.toString()})`) + + log(`Transferred ${formatEther(amount)} tokens, next sending to bridge`) + const tx2 = await contract.sendTokensToBridge() + await tx2.wait() + + const balance2 = await adminTokenMainnet.balanceOf(adminWalletMainnet.address) + log(`Token balance of ${adminWalletMainnet.address}: ${formatEther(balance2)} (${balance2.toString()})`) + + expect(formatEther(balance2.sub(balance1))).toEqual('0.2') + }, 150000) + +}) diff --git a/test/integration/dataunion/calculate.test.ts b/test/integration/dataunion/calculate.test.ts index 17fc03929..57d7002d6 100644 --- a/test/integration/dataunion/calculate.test.ts +++ b/test/integration/dataunion/calculate.test.ts @@ -3,9 +3,9 @@ import debug from 'debug' import StreamrClient from '../../../src/StreamrClient' import config from '../config' +import { createClient, expectInvalidAddress } from '../../utils' -const log = debug('StreamrClient::DataUnionEndpoints::integration-test-calculate') -// const { log } = console +const log = debug('StreamrClient::DataUnion::integration-test-calculate') // @ts-expect-error const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) @@ -16,28 +16,36 @@ const adminWalletMainnet = new Wallet(config.clientOptions.auth.privateKey, prov // This test will fail when new docker images are pushed with updated DU smart contracts // -> generate new codehashes for getDataUnionMainnetAddress() and getDataUnionSidechainAddress() -it('DataUnionEndPoints: calculate DU address before deployment', async () => { - log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) - const network = await providerMainnet.getNetwork() - log('Connected to "mainnet" network: ', JSON.stringify(network)) - const network2 = await providerSidechain.getNetwork() - log('Connected to sidechain network: ', JSON.stringify(network2)) +describe('DataUnion calculate', () => { - const adminClient = new StreamrClient(config.clientOptions as any) - await adminClient.ensureConnected() + afterAll(() => { + providerMainnet.removeAllListeners() + providerSidechain.removeAllListeners() + }) - const dataUnionName = 'test-' + Date.now() - // eslint-disable-next-line no-underscore-dangle - const dataUnionPredicted = adminClient._getDataUnionFromName({ dataUnionName, deployerAddress: adminWalletMainnet.address }) + it('calculate DU address before deployment', async () => { + log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) + const network = await providerMainnet.getNetwork() + log('Connected to "mainnet" network: ', JSON.stringify(network)) + const network2 = await providerSidechain.getNetwork() + log('Connected to sidechain network: ', JSON.stringify(network2)) - const dataUnionDeployed = await adminClient.deployDataUnion({ dataUnionName }) - const version = await dataUnionDeployed.getVersion() + const adminClient = new StreamrClient(config.clientOptions as any) - await providerMainnet.removeAllListeners() - await providerSidechain.removeAllListeners() - await adminClient.ensureDisconnected() + const dataUnionName = 'test-' + Date.now() + // eslint-disable-next-line no-underscore-dangle + const dataUnionPredicted = adminClient._getDataUnionFromName({ dataUnionName, deployerAddress: adminWalletMainnet.address }) - expect(dataUnionPredicted.getAddress()).toBe(dataUnionDeployed.getAddress()) - expect(dataUnionPredicted.getSidechainAddress()).toBe(dataUnionDeployed.getSidechainAddress()) - expect(version).toBe(2) -}, 60000) + const dataUnionDeployed = await adminClient.deployDataUnion({ dataUnionName }) + const version = await dataUnionDeployed.getVersion() + + expect(dataUnionPredicted.getAddress()).toBe(dataUnionDeployed.getAddress()) + expect(dataUnionPredicted.getSidechainAddress()).toBe(dataUnionDeployed.getSidechainAddress()) + expect(version).toBe(2) + }, 60000) + + it('get DataUnion: invalid address', () => { + const client = createClient(providerSidechain) + return expectInvalidAddress(async () => client.getDataUnion('invalid-address')) + }) +}) diff --git a/test/integration/dataunion/deploy.test.ts b/test/integration/dataunion/deploy.test.ts index aad865cca..6caca2e77 100644 --- a/test/integration/dataunion/deploy.test.ts +++ b/test/integration/dataunion/deploy.test.ts @@ -3,17 +3,16 @@ import debug from 'debug' import StreamrClient from '../../../src/StreamrClient' import config from '../config' +import { createMockAddress } from '../../utils' -const log = debug('StreamrClient::DataUnionEndpoints::integration-test-deploy') +const log = debug('StreamrClient::DataUnion::integration-test-deploy') // @ts-expect-error const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) // @ts-expect-error const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) -const createMockAddress = () => '0x000000000000000000000000000' + Date.now() - -describe('DataUnion deployment', () => { +describe('DataUnion deploy', () => { let adminClient: StreamrClient @@ -24,9 +23,13 @@ describe('DataUnion deployment', () => { const network2 = await providerSidechain.getNetwork() log('Connected to sidechain network: ', JSON.stringify(network2)) adminClient = new StreamrClient(config.clientOptions as any) - await adminClient.ensureConnected() }, 60000) + afterAll(() => { + providerMainnet.removeAllListeners() + providerSidechain.removeAllListeners() + }) + describe('owner', () => { it('not specified: defaults to deployer', async () => { diff --git a/test/integration/dataunion/member.test.ts b/test/integration/dataunion/member.test.ts index ca90b4f5c..68adf9a51 100644 --- a/test/integration/dataunion/member.test.ts +++ b/test/integration/dataunion/member.test.ts @@ -4,19 +4,17 @@ import debug from 'debug' import StreamrClient from '../../../src/StreamrClient' import config from '../config' import { DataUnion, JoinRequestState } from '../../../src/dataunion/DataUnion' -import { fakePrivateKey } from '../../utils' +import { createMockAddress, expectInvalidAddress, fakePrivateKey } from '../../utils' import authFetch from '../../../src/rest/authFetch' import { getEndpointUrl } from '../../../src/utils' -const log = debug('StreamrClient::DataUnionEndpoints::integration-test-member') +const log = debug('StreamrClient::DataUnion::integration-test-member') // @ts-expect-error const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) // @ts-expect-error const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) -const createMockAddress = () => '0x000000000000000000000000000' + Date.now() - const joinMember = async (memberWallet: Wallet, secret: string|undefined, dataUnionAddress: string) => { const memberClient = new StreamrClient({ ...config.clientOptions, @@ -24,7 +22,6 @@ const joinMember = async (memberWallet: Wallet, secret: string|undefined, dataUn privateKey: memberWallet.privateKey, } } as any) - await memberClient.ensureConnected() return memberClient.getDataUnion(dataUnionAddress).join(secret) } @@ -40,7 +37,6 @@ describe('DataUnion member', () => { const network2 = await providerSidechain.getNetwork() log('Connected to sidechain network: ', JSON.stringify(network2)) const adminClient = new StreamrClient(config.clientOptions as any) - await adminClient.ensureConnected() dataUnion = await adminClient.deployDataUnion() // product is needed for join requests to analyze the DU version const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') @@ -59,6 +55,11 @@ describe('DataUnion member', () => { secret = await dataUnion.createSecret() }, 60000) + afterAll(() => { + providerMainnet.removeAllListeners() + providerSidechain.removeAllListeners() + }) + it('random user is not a member', async () => { const userAddress = createMockAddress() const isMember = await dataUnion.isMember(userAddress) @@ -99,4 +100,11 @@ describe('DataUnion member', () => { expect(isMember).toBe(false) }, 60000) + it('invalid address', () => { + return Promise.all([ + expectInvalidAddress(() => dataUnion.addMembers(['invalid-address'])), + expectInvalidAddress(() => dataUnion.removeMembers(['invalid-address'])), + expectInvalidAddress(() => dataUnion.isMember('invalid-address')) + ]) + }) }) diff --git a/test/integration/dataunion/signature.test.ts b/test/integration/dataunion/signature.test.ts index 000befdea..d90a24fda 100644 --- a/test/integration/dataunion/signature.test.ts +++ b/test/integration/dataunion/signature.test.ts @@ -9,66 +9,70 @@ import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' import config from '../config' import authFetch from '../../../src/rest/authFetch' -const log = debug('StreamrClient::DataUnionEndpoints::integration-test-signature') +const log = debug('StreamrClient::DataUnion::integration-test-signature') // @ts-expect-error const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) const adminWalletSidechain = new Wallet(config.clientOptions.auth.privateKey, providerSidechain) -it('DataUnion signature', async () => { +describe('DataUnion signature', () => { - const adminClient = new StreamrClient(config.clientOptions as any) - await adminClient.ensureConnected() - const dataUnion = await adminClient.deployDataUnion() - const secret = await dataUnion.createSecret('DataUnionEndpoints test secret') - log(`DataUnion ${dataUnion.getAddress()} is ready to roll`) + afterAll(() => { + providerSidechain.removeAllListeners() + }) + + it('check validity', async () => { + const adminClient = new StreamrClient(config.clientOptions as any) + const dataUnion = await adminClient.deployDataUnion() + const secret = await dataUnion.createSecret('test secret') + log(`DataUnion ${dataUnion.getAddress()} is ready to roll`) - const memberWallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) - const member2Wallet = new Wallet(`0x100000000000000000000000000000000000000012300000002${Date.now()}`, providerSidechain) + const memberWallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) + const member2Wallet = new Wallet(`0x100000000000000000000000000000000000000012300000002${Date.now()}`, providerSidechain) - const memberClient = new StreamrClient({ - ...config.clientOptions, - auth: { - privateKey: memberWallet.privateKey - } - } as any) - await memberClient.ensureConnected() + const memberClient = new StreamrClient({ + ...config.clientOptions, + auth: { + privateKey: memberWallet.privateKey + } + } as any) - // product is needed for join requests to analyze the DU version - const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') - await authFetch(createProductUrl, adminClient.session, { - method: 'POST', - body: JSON.stringify({ - beneficiaryAddress: dataUnion.getAddress(), - type: 'DATAUNION', - dataUnionVersion: 2 + // product is needed for join requests to analyze the DU version + const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') + await authFetch(createProductUrl, adminClient.session, { + method: 'POST', + body: JSON.stringify({ + beneficiaryAddress: dataUnion.getAddress(), + type: 'DATAUNION', + dataUnionVersion: 2 + }) }) - }) - await memberClient.getDataUnion(dataUnion.getAddress()).join(secret) + await memberClient.getDataUnion(dataUnion.getAddress()).join(secret) - // eslint-disable-next-line no-underscore-dangle - const contract = await dataUnion._getContract() - const sidechainContract = new Contract(contract.sidechain.address, DataUnionSidechain.abi, adminWalletSidechain) - const tokenSidechain = new Contract(config.clientOptions.tokenAddressSidechain, Token.abi, adminWalletSidechain) + // eslint-disable-next-line no-underscore-dangle + const contract = await dataUnion._getContract() + const sidechainContract = new Contract(contract.sidechain.address, DataUnionSidechain.abi, adminWalletSidechain) + const tokenSidechain = new Contract(config.clientOptions.tokenAddressSidechain, Token.abi, adminWalletSidechain) - const signature = await memberClient.getDataUnion(dataUnion.getAddress()).signWithdrawAllTo(member2Wallet.address) - const signature2 = await memberClient - .getDataUnion(dataUnion.getAddress()) - .signWithdrawAmountTo(member2Wallet.address, parseEther('1')) - const signature3 = await memberClient - .getDataUnion(dataUnion.getAddress()) - .signWithdrawAmountTo(member2Wallet.address, 3000000000000000) // 0.003 tokens + const signature = await memberClient.getDataUnion(dataUnion.getAddress()).signWithdrawAllTo(member2Wallet.address) + const signature2 = await memberClient + .getDataUnion(dataUnion.getAddress()) + .signWithdrawAmountTo(member2Wallet.address, parseEther('1')) + const signature3 = await memberClient + .getDataUnion(dataUnion.getAddress()) + .signWithdrawAmountTo(member2Wallet.address, 3000000000000000) // 0.003 tokens - const isValid = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, '0', signature) // '0' = all earnings - const isValid2 = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, parseEther('1'), signature2) - const isValid3 = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, '3000000000000000', signature3) - log(`Signature for all tokens ${memberWallet.address} -> ${member2Wallet.address}: ${signature}, checked ${isValid ? 'OK' : '!!!BROKEN!!!'}`) - log(`Signature for 1 token ${memberWallet.address} -> ${member2Wallet.address}: ${signature2}, checked ${isValid2 ? 'OK' : '!!!BROKEN!!!'}`) - log(`Signature for 0.003 tokens ${memberWallet.address} -> ${member2Wallet.address}: ${signature3}, checked ${isValid3 ? 'OK' : '!!!BROKEN!!!'}`) - log(`sidechainDU(${sidechainContract.address}) token bal ${await tokenSidechain.balanceOf(sidechainContract.address)}`) + const isValid = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, '0', signature) // '0' = all earnings + const isValid2 = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, parseEther('1'), signature2) + const isValid3 = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, '3000000000000000', signature3) + log(`Signature for all tokens ${memberWallet.address} -> ${member2Wallet.address}: ${signature}, checked ${isValid ? 'OK' : '!!!BROKEN!!!'}`) + log(`Signature for 1 token ${memberWallet.address} -> ${member2Wallet.address}: ${signature2}, checked ${isValid2 ? 'OK' : '!!!BROKEN!!!'}`) + log(`Signature for 0.003 tokens ${memberWallet.address} -> ${member2Wallet.address}: ${signature3}, checked ${isValid3 ? 'OK' : '!!!BROKEN!!!'}`) + log(`sidechainDU(${sidechainContract.address}) token bal ${await tokenSidechain.balanceOf(sidechainContract.address)}`) - expect(isValid).toBe(true) - expect(isValid2).toBe(true) - expect(isValid3).toBe(true) + expect(isValid).toBe(true) + expect(isValid2).toBe(true) + expect(isValid3).toBe(true) + }, 100000) -}, 100000) +}) diff --git a/test/integration/dataunion/stats.test.ts b/test/integration/dataunion/stats.test.ts new file mode 100644 index 000000000..7ea3a36ae --- /dev/null +++ b/test/integration/dataunion/stats.test.ts @@ -0,0 +1,98 @@ +import { providers } from 'ethers' +import debug from 'debug' + +import StreamrClient from '../../../src/StreamrClient' +import config from '../config' +import { DataUnion, MemberStatus } from '../../../src/dataunion/DataUnion' +import { createClient, createMockAddress, expectInvalidAddress } from '../../utils' +import { BigNumber } from '@ethersproject/bignumber' + +const log = debug('StreamrClient::DataUnion::integration-test-stats') + +// @ts-expect-error +const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) +// @ts-expect-error +const providerMainnet = new providers.JsonRpcProvider(config.clientOptions.mainnet) + +describe('DataUnion stats', () => { + + let adminClient: StreamrClient + let dataUnion: DataUnion + let queryClient: StreamrClient + const nonce = Date.now() + const activeMemberAddressList = [ + `0x100000000000000000000000000${nonce}`, + `0x200000000000000000000000000${nonce}`, + `0x300000000000000000000000000${nonce}`, + ] + const inactiveMember = createMockAddress() + + beforeAll(async () => { + log(`Connecting to Ethereum networks, config = ${JSON.stringify(config)}`) + const network = await providerMainnet.getNetwork() + log('Connected to "mainnet" network: ', JSON.stringify(network)) + const network2 = await providerSidechain.getNetwork() + log('Connected to sidechain network: ', JSON.stringify(network2)) + adminClient = new StreamrClient(config.clientOptions as any) + dataUnion = await adminClient.deployDataUnion() + await dataUnion.addMembers(activeMemberAddressList.concat([inactiveMember])) + await dataUnion.removeMembers([inactiveMember]) + queryClient = createClient(providerSidechain) + }, 60000) + + afterAll(() => { + providerMainnet.removeAllListeners() + providerSidechain.removeAllListeners() + }) + + it('DataUnion stats', async () => { + const stats = await queryClient.getDataUnion(dataUnion.getAddress()).getStats() + expect(stats.activeMemberCount).toEqual(BigNumber.from(3)) + expect(stats.inactiveMemberCount).toEqual(BigNumber.from(1)) + expect(stats.joinPartAgentCount).toEqual(BigNumber.from(2)) + expect(stats.totalEarnings).toEqual(BigNumber.from(0)) + expect(stats.totalWithdrawable).toEqual(BigNumber.from(0)) + expect(stats.lifetimeMemberEarnings).toEqual(BigNumber.from(0)) + }, 150000) + + it('member stats', async () => { + const memberStats = await Promise.all(activeMemberAddressList.concat([inactiveMember]).map((m) => queryClient.getDataUnion(dataUnion.getAddress()).getMemberStats(m))) + const ZERO = BigNumber.from(0) + expect(memberStats).toMatchObject([{ + status: MemberStatus.ACTIVE, + earningsBeforeLastJoin: ZERO, + totalEarnings: ZERO, + withdrawableEarnings: ZERO, + }, { + status: MemberStatus.ACTIVE, + earningsBeforeLastJoin: ZERO, + totalEarnings: ZERO, + withdrawableEarnings: ZERO, + }, { + status: MemberStatus.ACTIVE, + earningsBeforeLastJoin: ZERO, + totalEarnings: ZERO, + withdrawableEarnings: ZERO, + }, { + status: MemberStatus.INACTIVE, + earningsBeforeLastJoin: ZERO, + totalEarnings: ZERO, + withdrawableEarnings: ZERO, + }]) + }, 150000) + + it('member stats: no member', async () => { + const memberStats = await queryClient.getDataUnion(dataUnion.getAddress()).getMemberStats(createMockAddress()) + const ZERO = BigNumber.from(0) + expect(memberStats).toMatchObject({ + status: MemberStatus.NONE, + earningsBeforeLastJoin: ZERO, + totalEarnings: ZERO, + withdrawableEarnings: ZERO + }) + }) + + it('member stats: invalid address', () => { + return expectInvalidAddress(() => dataUnion.getMemberStats('invalid-address')) + }) +}) diff --git a/test/integration/dataunion/withdraw.test.ts b/test/integration/dataunion/withdraw.test.ts index dd8ce036c..cba025574 100644 --- a/test/integration/dataunion/withdraw.test.ts +++ b/test/integration/dataunion/withdraw.test.ts @@ -9,9 +9,10 @@ import * as Token from '../../../contracts/TestToken.json' import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' import config from '../config' import authFetch from '../../../src/rest/authFetch' +import { createClient, createMockAddress, expectInvalidAddress } from '../../utils' +import { MemberStatus } from '../../../src/dataunion/DataUnion' -const log = debug('StreamrClient::DataUnionEndpoints::integration-test-withdraw') -// const { log } = console +const log = debug('StreamrClient::DataUnion::integration-test-withdraw') // @ts-expect-error const providerSidechain = new providers.JsonRpcProvider(config.clientOptions.sidechain) @@ -45,10 +46,9 @@ const testWithdraw = async ( await tx1.wait() const adminClient = new StreamrClient(config.clientOptions as any) - await adminClient.ensureConnected() const dataUnion = await adminClient.deployDataUnion() - const secret = await dataUnion.createSecret('DataUnionEndpoints test secret') + const secret = await dataUnion.createSecret('test secret') log(`DataUnion ${dataUnion.getAddress()} is ready to roll`) // dataUnion = await adminClient.getDataUnionContract({dataUnion: "0xd778CfA9BB1d5F36E42526B2BAFD07B74b4066c0"}) @@ -69,7 +69,6 @@ const testWithdraw = async ( privateKey: memberWallet.privateKey } } as any) - await memberClient.ensureConnected() // product is needed for join requests to analyze the DU version const createProductUrl = getEndpointUrl(config.clientOptions.restUrl, 'products') @@ -142,17 +141,11 @@ const testWithdraw = async ( const balanceAfter = await getBalanceAfter(memberWallet, adminTokenMainnet) const balanceIncrease = balanceAfter.sub(balanceBefore) - await providerMainnet.removeAllListeners() - await providerSidechain.removeAllListeners() - await memberClient.ensureDisconnected() - await adminClient.ensureDisconnected() - expect(stats).toMatchObject({ - status: 'active', - earningsBeforeLastJoin: '0', - lmeAtJoin: '0', - totalEarnings: '1000000000000000000', - withdrawableEarnings: '1000000000000000000', + status: MemberStatus.ACTIVE, + earningsBeforeLastJoin: BigNumber.from(0), + totalEarnings: BigNumber.from('1000000000000000000'), + withdrawableEarnings: BigNumber.from('1000000000000000000') }) expect(withdrawTr.logs[0].address).toBe(config.clientOptions.tokenAddressSidechain) expect(balanceIncrease.toString()).toBe(amount.toString()) @@ -160,6 +153,11 @@ const testWithdraw = async ( describe('DataUnion withdraw', () => { + afterAll(() => { + providerMainnet.removeAllListeners() + providerSidechain.removeAllListeners() + }) + describe('Member', () => { it('by member itself', () => { @@ -208,4 +206,16 @@ describe('DataUnion withdraw', () => { }, 300000) }) + it('Validate address', async () => { + const client = createClient(providerSidechain) + const dataUnion = client.getDataUnion(createMockAddress()) + return Promise.all([ + expectInvalidAddress(() => dataUnion.getWithdrawableEarnings('invalid-address')), + expectInvalidAddress(() => dataUnion.withdrawAllTo('invalid-address')), + expectInvalidAddress(() => dataUnion.signWithdrawAllTo('invalid-address')), + expectInvalidAddress(() => dataUnion.signWithdrawAmountTo('invalid-address', '123')), + expectInvalidAddress(() => dataUnion.withdrawAllToMember('invalid-address')), + expectInvalidAddress(() => dataUnion.withdrawAllToSigned('invalid-address', 'invalid-address', 'mock-signature')) + ]) + }) }) diff --git a/test/utils.js b/test/utils.ts similarity index 75% rename from test/utils.js rename to test/utils.ts index 15dbd7632..7d2e92ce9 100644 --- a/test/utils.js +++ b/test/utils.ts @@ -1,21 +1,22 @@ import { inspect } from 'util' - import { wait } from 'streamr-test-utils' - +import { providers, Wallet } from 'ethers' import { pTimeout, counterId, AggregatedError } from '../src/utils' import { validateOptions } from '../src/stream/utils' +import StreamrClient from '../src/StreamrClient' const crypto = require('crypto') +const config = require('./integration/config') -export const uid = (prefix) => counterId(`p${process.pid}${prefix ? '-' + prefix : ''}`) +export const uid = (prefix?: string) => counterId(`p${process.pid}${prefix ? '-' + prefix : ''}`) export function fakePrivateKey() { return crypto.randomBytes(32).toString('hex') } -const TEST_REPEATS = parseInt(process.env.TEST_REPEATS, 10) || 1 +const TEST_REPEATS = (process.env.TEST_REPEATS) ? parseInt(process.env.TEST_REPEATS, 10) : 1 -export function describeRepeats(msg, fn, describeFn = describe) { +export function describeRepeats(msg: any, fn: any, describeFn = describe) { for (let k = 0; k < TEST_REPEATS; k++) { // eslint-disable-next-line no-loop-func describe(msg, () => { @@ -24,16 +25,16 @@ export function describeRepeats(msg, fn, describeFn = describe) { } } -describeRepeats.skip = (msg, fn) => { +describeRepeats.skip = (msg: any, fn: any) => { describe.skip(`${msg} – test repeat ALL of ${TEST_REPEATS}`, fn) } -describeRepeats.only = (msg, fn) => { +describeRepeats.only = (msg: any, fn: any) => { describeRepeats(msg, fn, describe.only) } -export async function collect(iterator, fn = async () => {}) { - const received = [] +export async function collect(iterator: any, fn: (item: any) => void = async () => {}) { + const received: any[] = [] for await (const msg of iterator) { received.push(msg.getParsedContent()) await fn({ @@ -45,24 +46,25 @@ export async function collect(iterator, fn = async () => {}) { } export function addAfterFn() { - const afterFns = [] + const afterFns: any[] = [] afterEach(async () => { const fns = afterFns.slice() afterFns.length = 0 + // @ts-expect-error AggregatedError.throwAllSettled(await Promise.allSettled(fns.map((fn) => fn()))) }) - return (fn) => { + return (fn: any) => { afterFns.push(fn) } } -export const Msg = (opts) => ({ +export const Msg = (opts: any) => ({ value: uid('msg'), ...opts, }) -function defaultMessageMatchFn(msgTarget, msgGot) { +function defaultMessageMatchFn(msgTarget: any, msgGot: any) { if (msgTarget.streamMessage.signature) { // compare signatures by default return msgTarget.streamMessage.signature === msgGot.signature @@ -70,9 +72,9 @@ function defaultMessageMatchFn(msgTarget, msgGot) { return JSON.stringify(msgGot.content) === JSON.stringify(msgTarget.streamMessage.getParsedContent()) } -export function getWaitForStorage(client, defaultOpts = {}) { +export function getWaitForStorage(client: StreamrClient, defaultOpts = {}) { /* eslint-disable no-await-in-loop */ - return async (publishRequest, opts = {}) => { + return async (publishRequest: any, opts = {}) => { const { streamId, streamPartition = 0, interval = 500, timeout = 5000, count = 100, messageMatchFn = defaultMessageMatchFn } = validateOptions({ @@ -96,14 +98,15 @@ export function getWaitForStorage(client, defaultOpts = {}) { duration }, { publishRequest, - last: last.map((l) => l.content), + last: last.map((l: any) => l.content), }) - const err = new Error(`timed out after ${duration}ms waiting for message`) + const err: any = new Error(`timed out after ${duration}ms waiting for message`) err.publishRequest = publishRequest throw err } last = await client.getStreamLast({ + // @ts-expect-error streamId, streamPartition, count, @@ -118,7 +121,7 @@ export function getWaitForStorage(client, defaultOpts = {}) { client.debug('message not found, retrying... %o', { msg: publishRequest.streamMessage.getParsedContent(), - last: last.map(({ content }) => content) + last: last.map(({ content }: any) => content) }) await wait(interval) @@ -127,7 +130,7 @@ export function getWaitForStorage(client, defaultOpts = {}) { /* eslint-enable no-await-in-loop */ } -export function getPublishTestMessages(client, defaultOpts = {}) { +export function getPublishTestMessages(client: StreamrClient, defaultOpts = {}) { // second argument could also be streamId if (typeof defaultOpts === 'string') { // eslint-disable-next-line no-param-reassign @@ -147,7 +150,7 @@ export function getPublishTestMessages(client, defaultOpts = {}) { waitForLast = false, // wait for message to hit storage waitForLastCount, waitForLastTimeout, - beforeEach = (m) => m, + beforeEach = (m: any) => m, afterEach = () => {}, timestamp, partitionKey, @@ -210,7 +213,7 @@ export function getPublishTestMessages(client, defaultOpts = {}) { streamPartition, timeout: waitForLastTimeout, count: waitForLastCount, - messageMatchFn(m, b) { + messageMatchFn(m: any, b: any) { checkDone() return m.streamMessage.signature === b.signature } @@ -223,7 +226,7 @@ export function getPublishTestMessages(client, defaultOpts = {}) { } } - const publishTestMessages = async (...args) => { + const publishTestMessages = async (...args: any[]) => { const published = await publishTestMessagesRaw(...args) return published.map(([msg]) => msg) } @@ -231,3 +234,19 @@ export function getPublishTestMessages(client, defaultOpts = {}) { publishTestMessages.raw = publishTestMessagesRaw return publishTestMessages } + +export const createMockAddress = () => '0x000000000000000000000000000' + Date.now() + +export const createClient = (providerSidechain: providers.JsonRpcProvider) => { + const wallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) + return new StreamrClient({ + ...config.clientOptions, + auth: { + privateKey: wallet.privateKey + } + }) +} + +export const expectInvalidAddress = (operation: () => Promise) => { + return expect(() => operation()).rejects.toThrow('invalid address') +} From e0474533f61782580f68108e072826b7cb547260 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 2 Mar 2021 13:41:42 +0200 Subject: [PATCH 504/517] Types for authFetch (#207) Type annotations for authFetch endpoints (and utils.test.ts). --- package-lock.json | 102 +++++++++++++++++++++ package.json | 3 + src/dataunion/DataUnion.ts | 6 +- src/rest/LoginEndpoints.ts | 20 ++-- src/rest/StreamEndpoints.ts | 49 +++++++--- src/rest/{authFetch.js => authFetch.ts} | 15 ++- src/stream/index.ts | 46 ++++++++-- test/unit/{utils.test.js => utils.test.ts} | 23 +++-- test/utils.ts | 4 +- 9 files changed, 221 insertions(+), 47 deletions(-) rename src/rest/{authFetch.js => authFetch.ts} (83%) rename test/unit/{utils.test.js => utils.test.ts} (83%) diff --git a/package-lock.json b/package-lock.json index 5a1a2dda0..19ba7d60c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2361,6 +2361,25 @@ "@babel/types": "^7.3.0" } }, + "@types/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-W98JrE0j2K78swW4ukqMleo8R7h/pFETjM2DQ90MF6XK2i4LO4W3gQ71Lt4w3bfm2EvVSyWHplECvB5sK22yFQ==", + "dev": true, + "requires": { + "@types/connect": "*", + "@types/node": "*" + } + }, + "@types/connect": { + "version": "3.4.34", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", + "integrity": "sha512-ePPA/JuI+X0vb+gSWlPKOY0NdNAie/rPUqX2GUPpbZwiKTkSPhjXWuee47E4MtE54QVzGCQMQkAL6JhV2E1+cQ==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/debug": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.5.tgz", @@ -2393,6 +2412,29 @@ "integrity": "sha512-laIjwTQaD+5DukBZaygQ79K1Z0jb1bPEMRrkXSLjtCcZm+abyp5YbrqpSLzD42FwWW6gK/aS4NYpJ804nG2brg==", "dev": true }, + "@types/express": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.11.tgz", + "integrity": "sha512-no+R6rW60JEc59977wIxreQVsIEOAYwgCqldrA/vkpCnbD7MqTefO97lmoBe4WE0F156bC4uLSP1XHDOySnChg==", + "dev": true, + "requires": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "@types/express-serve-static-core": { + "version": "4.17.18", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.18.tgz", + "integrity": "sha512-m4JTwx5RUBNZvky/JJ8swEJPKFd8si08pPF2PfizYjGZOKr/svUWPcoUmLow6MmPzhasphB7gSTINY67xn3JNA==", + "dev": true, + "requires": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" + } + }, "@types/graceful-fs": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", @@ -2463,12 +2505,41 @@ "@types/lodash": "*" } }, + "@types/mime": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", + "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", + "dev": true + }, "@types/node": { "version": "14.14.31", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.31.tgz", "integrity": "sha512-vFHy/ezP5qI0rFgJ7aQnjDXwAMrG0KqqIH7tQG5PPv3BWBayOPIQNBjVc/P6hhdZfMx51REc6tfDNXHUio893g==", "dev": true }, + "@types/node-fetch": { + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.5.8.tgz", + "integrity": "sha512-fbjI6ja0N5ZA8TV53RUqzsKNkl9fv8Oj3T7zxW7FGv1GSH7gwJaNF8dzCjrqKaxKeUpTz4yT1DaJFq/omNpGfw==", + "dev": true, + "requires": { + "@types/node": "*", + "form-data": "^3.0.0" + }, + "dependencies": { + "form-data": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-3.0.1.tgz", + "integrity": "sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==", + "dev": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "@types/normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -2487,6 +2558,37 @@ "integrity": "sha512-/JHkVHtx/REVG0VVToGRGH2+23hsYLHdyG+GrvoUGlGAd0ErauXDyvHtRI/7H7mzLm+tBCKA7pfcpkQ1lf58iQ==", "dev": true }, + "@types/range-parser": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz", + "integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==", + "dev": true + }, + "@types/serve-static": { + "version": "1.13.9", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.9.tgz", + "integrity": "sha512-ZFqF6qa48XsPdjXV5Gsz0Zqmux2PerNd3a/ktL45mHpa19cuMi/cL8tcxdAx497yRh+QtYPuofjT9oWw9P7nkA==", + "dev": true, + "requires": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "@types/sinon": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-9.0.10.tgz", + "integrity": "sha512-/faDC0erR06wMdybwI/uR8wEKV/E83T0k4sepIpB7gXuy2gzx2xiOjmztq6a2Y6rIGJ04D+6UU0VBmWy+4HEMA==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz", + "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg==", + "dev": true + }, "@types/stack-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", diff --git a/package.json b/package.json index 5ffed06f4..8f8e0bd1b 100644 --- a/package.json +++ b/package.json @@ -58,10 +58,13 @@ "@babel/preset-typescript": "^7.12.17", "@tsconfig/node14": "^1.0.0", "@types/debug": "^4.1.5", + "@types/express": "^4.17.11", "@types/jest": "^26.0.20", "@types/lodash.uniqueid": "^4.0.6", "@types/node": "^14.14.31", + "@types/node-fetch": "^2.5.8", "@types/qs": "^6.9.5", + "@types/sinon": "^9.0.10", "@types/uuid": "^8.3.0", "@typescript-eslint/eslint-plugin": "^4.15.1", "@typescript-eslint/parser": "^4.15.1", diff --git a/src/dataunion/DataUnion.ts b/src/dataunion/DataUnion.ts index 70d72c1a4..a499bb387 100644 --- a/src/dataunion/DataUnion.ts +++ b/src/dataunion/DataUnion.ts @@ -92,7 +92,7 @@ export class DataUnion { /** * Send a joinRequest, or get into data union instantly with a data union secret */ - async join(secret?: string): Promise { + async join(secret?: string): Promise { const memberAddress = this.client.getAddress() as string const body: any = { memberAddress @@ -100,7 +100,7 @@ export class DataUnion { if (secret) { body.secret = secret } const url = getEndpointUrl(this.client.options.restUrl, 'dataunions', this.contractAddress, 'joinRequests') - const response = await authFetch( + const response = await authFetch( url, this.client.session, { @@ -330,7 +330,7 @@ export class DataUnion { */ async createSecret(name: string = 'Untitled Data Union Secret'): Promise { const url = getEndpointUrl(this.client.options.restUrl, 'dataunions', this.contractAddress, 'secrets') - const res = await authFetch( + const res = await authFetch<{secret: string}>( url, this.client.session, { diff --git a/src/rest/LoginEndpoints.ts b/src/rest/LoginEndpoints.ts index af281830f..4a70bf49c 100644 --- a/src/rest/LoginEndpoints.ts +++ b/src/rest/LoginEndpoints.ts @@ -3,8 +3,16 @@ import { getEndpointUrl } from '../utils' import authFetch, { AuthFetchError } from './authFetch' +export interface UserDetails { + name: string + username: string + imageUrlSmall?: string + imageUrlLarge?: string + lastLogin?: string +} + async function getSessionToken(url: string, props: any) { - return authFetch( + return authFetch<{ token: string }>( url, undefined, { @@ -30,7 +38,7 @@ export class LoginEndpoints { address, }) const url = getEndpointUrl(this.client.options.restUrl, 'login', 'challenge', address) - return authFetch( + return authFetch<{ challenge: string }>( url, undefined, { @@ -39,7 +47,7 @@ export class LoginEndpoints { ) } - async sendChallengeResponse(challenge: string, signature: string, address: string) { + async sendChallengeResponse(challenge: { challenge: string }, signature: string, address: string) { this.client.debug('sendChallengeResponse %o', { challenge, signature, @@ -98,12 +106,12 @@ export class LoginEndpoints { async getUserInfo() { this.client.debug('getUserInfo') - return authFetch(`${this.client.options.restUrl}/users/me`, this.client.session) + return authFetch(`${this.client.options.restUrl}/users/me`, this.client.session) } - async logoutEndpoint() { + async logoutEndpoint(): Promise { this.client.debug('logoutEndpoint') - return authFetch(`${this.client.options.restUrl}/logout`, this.client.session, { + await authFetch(`${this.client.options.restUrl}/logout`, this.client.session, { method: 'POST', }) } diff --git a/src/rest/StreamEndpoints.ts b/src/rest/StreamEndpoints.ts index f56e6349c..01c1c5a6a 100644 --- a/src/rest/StreamEndpoints.ts +++ b/src/rest/StreamEndpoints.ts @@ -13,6 +13,8 @@ import { isKeyExchangeStream } from '../stream/KeyExchange' import authFetch from './authFetch' import { Todo } from '../types' import StreamrClient from '../StreamrClient' +// TODO change this import when streamr-client-protocol exports StreamMessage type or the enums types directly +import { ContentType, EncryptionType, SignatureType, StreamMessageType } from 'streamr-client-protocol/dist/src/protocol/message_layer/StreamMessage' const debug = debugFactory('StreamrClient') @@ -30,6 +32,28 @@ export interface StreamListQuery { operation?: StreamOperation } +export interface StreamValidationInfo { + partitions: number, + requireSignedData: boolean + requireEncryptedData: boolean +} + +export interface StreamMessageAsObject { // TODO this could be in streamr-protocol + streamId: string + streamPartition: number + timestamp: number + sequenceNumber: number + publisherId: string + msgChainId: string + messageType: StreamMessageType + contentType: ContentType + encryptionType: EncryptionType + groupKeyId: string|null + content: any + signatureType: SignatureType + signature: string|null +} + const agentSettings = { keepAlive: true, keepAliveMsecs: 5000, @@ -74,7 +98,7 @@ export class StreamEndpoints { const url = getEndpointUrl(this.client.options.restUrl, 'streams', streamId) try { - const json = await authFetch(url, this.client.session) + const json = await authFetch(url, this.client.session) return new Stream(this.client, json) } catch (e) { if (e.response && e.response.status === 404) { @@ -89,8 +113,8 @@ export class StreamEndpoints { query, }) const url = getEndpointUrl(this.client.options.restUrl, 'streams') + '?' + qs.stringify(query) - const json = await authFetch(url, this.client.session) - return json ? json.map((stream: any) => new Stream(this.client, stream)) : [] + const json = await authFetch(url, this.client.session) + return json ? json.map((stream: StreamProperties) => new Stream(this.client, stream)) : [] } async getStreamByName(name: string) { @@ -110,7 +134,7 @@ export class StreamEndpoints { props, }) - const json = await authFetch( + const json = await authFetch( getEndpointUrl(this.client.options.restUrl, 'streams'), this.client.session, { @@ -153,7 +177,7 @@ export class StreamEndpoints { streamId, }) const url = getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'publishers') - const json = await authFetch(url, this.client.session) + const json = await authFetch<{ addresses: string[]}>(url, this.client.session) return json.addresses.map((a: string) => a.toLowerCase()) } @@ -180,7 +204,7 @@ export class StreamEndpoints { streamId, }) const url = getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'subscribers') - const json = await authFetch(url, this.client.session) + const json = await authFetch<{ addresses: string[] }>(url, this.client.session) return json.addresses.map((a: string) => a.toLowerCase()) } @@ -206,11 +230,11 @@ export class StreamEndpoints { streamId, }) const url = getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'validation') - const json = await authFetch(url, this.client.session) + const json = await authFetch(url, this.client.session) return json } - async getStreamLast(streamObjectOrId: Stream|string) { + async getStreamLast(streamObjectOrId: Stream|string): Promise { const { streamId, streamPartition = 0, count = 1 } = validateOptions(streamObjectOrId) this.client.debug('getStreamLast %o', { streamId, @@ -223,14 +247,15 @@ export class StreamEndpoints { + `?${qs.stringify({ count })}` ) - const json = await authFetch(url, this.client.session) + const json = await authFetch(url, this.client.session) return json } async getStreamPartsByStorageNode(address: string) { - const json = await authFetch(getEndpointUrl(this.client.options.restUrl, 'storageNodes', address, 'streams'), this.client.session) + type ItemType = { id: string, partitions: number} + const json = await authFetch(getEndpointUrl(this.client.options.restUrl, 'storageNodes', address, 'streams'), this.client.session) let result: StreamPart[] = [] - json.forEach((stream: { id: string, partitions: number }) => { + json.forEach((stream: ItemType) => { result = result.concat(StreamPart.fromStream(stream)) }) return result @@ -248,7 +273,7 @@ export class StreamEndpoints { }) // Send data to the stream - return authFetch( + await authFetch( getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'data'), this.client.session, { diff --git a/src/rest/authFetch.js b/src/rest/authFetch.ts similarity index 83% rename from src/rest/authFetch.js rename to src/rest/authFetch.ts index 7fd8b2297..55083faf4 100644 --- a/src/rest/authFetch.js +++ b/src/rest/authFetch.ts @@ -1,14 +1,18 @@ -import fetch from 'node-fetch' +import fetch, { Response } from 'node-fetch' import Debug from 'debug' import { getVersionString } from '../utils' +import Session from '../Session' export const DEFAULT_HEADERS = { 'Streamr-Client': `streamr-client-javascript/${getVersionString()}`, } export class AuthFetchError extends Error { - constructor(message, response, body) { + response: Response + body?: any + + constructor(message: string, response: Response, body?: any) { // add leading space if there is a body set const bodyMessage = body ? ` ${(typeof body === 'string' ? body : JSON.stringify(body).slice(0, 1024))}...` : '' super(message + bodyMessage) @@ -25,7 +29,7 @@ const debug = Debug('StreamrClient:utils:authfetch') // TODO: could use the debu let ID = 0 -export default async function authFetch(url, session, opts, requireNewToken = false) { +export default async function authFetch(url: string, session?: Session, opts?: any, requireNewToken = false): Promise { ID += 1 const timeStart = Date.now() const id = ID @@ -44,7 +48,7 @@ export default async function authFetch(url, session, opts, requireNewToken = fa debug('%d %s >> %o', id, url, opts) - const response = await fetch(url, { + const response: Response = await fetch(url, { ...opts, headers: { ...(session && !session.options.unauthenticated ? { @@ -54,6 +58,7 @@ export default async function authFetch(url, session, opts, requireNewToken = fa }, }) const timeEnd = Date.now() + // @ts-expect-error debug('%d %s << %d %s %s %s', id, url, response.status, response.statusText, Debug.humanize(timeEnd - timeStart)) const body = await response.text() @@ -67,7 +72,7 @@ export default async function authFetch(url, session, opts, requireNewToken = fa } } else if ([400, 401].includes(response.status) && !requireNewToken) { debug('%d %s – revalidating session') - return authFetch(url, session, options, true) + return authFetch(url, session, options, true) } else { debug('%d %s – failed', id, url) throw new AuthFetchError(`Request ${id} to ${url} returned with error code ${response.status}.`, response, body) diff --git a/src/stream/index.ts b/src/stream/index.ts index aad96d989..b0b6a0595 100644 --- a/src/stream/index.ts +++ b/src/stream/index.ts @@ -5,6 +5,20 @@ import StorageNode from './StorageNode' import StreamrClient from '../StreamrClient' import { Todo } from '../types' +interface StreamPermisionBase { + operation: StreamOperation +} + +export interface UserStreamPermission extends StreamPermisionBase { + user: string +} + +export interface AnonymousStreamPermisson extends StreamPermisionBase { + anonymous: true +} + +export type StreamPermision = UserStreamPermission | AnonymousStreamPermisson + export enum StreamOperation { STREAM_GET = 'stream_get', STREAM_EDIT = 'stream_edit', @@ -14,7 +28,19 @@ export enum StreamOperation { STREAM_SHARE = 'stream_share' } -export type StreamProperties = Todo +export interface StreamProperties { + id?: string + name?: string + description?: string + config?: { + fields: Field[]; + } + partitions?: number + requireSignedData?: boolean + requireEncryptedData?: boolean + storageDays?: number + inactivityThresholdHours?: number +} const VALID_FIELD_TYPES = ['number', 'string', 'boolean', 'list', 'map'] as const @@ -59,7 +85,7 @@ export default class Stream { } async update() { - const json = await authFetch( + const json = await authFetch( getEndpointUrl(this._client.options.restUrl, 'streams', this.id), this._client.session, { @@ -82,7 +108,7 @@ export default class Stream { } async delete() { - return authFetch( + await authFetch( getEndpointUrl(this._client.options.restUrl, 'streams', this.id), this._client.session, { @@ -92,14 +118,14 @@ export default class Stream { } async getPermissions() { - return authFetch( + return authFetch( getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'permissions'), this._client.session, ) } async getMyPermissions() { - return authFetch( + return authFetch( getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'permissions', 'me'), this._client.session, ) @@ -134,7 +160,7 @@ export default class Stream { permissionObject.anonymous = true } - return authFetch( + return authFetch( getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'permissions'), this._client.session, { @@ -145,7 +171,7 @@ export default class Stream { } async revokePermission(permissionId: number) { - return authFetch( + await authFetch( getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'permissions', String(permissionId)), this._client.session, { @@ -183,7 +209,7 @@ export default class Stream { } async addToStorageNode(address: string) { - return authFetch( + await authFetch( getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'storageNodes'), this._client.session, { @@ -196,7 +222,7 @@ export default class Stream { } async removeFromStorageNode(address: string) { - return authFetch( + await authFetch( getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'storageNodes', address), this._client.session, { @@ -206,7 +232,7 @@ export default class Stream { } async getStorageNodes() { - const json = await authFetch( + const json = await authFetch<{ storageNodeAddress: string}[] >( getEndpointUrl(this._client.options.restUrl, 'streams', this.id, 'storageNodes'), this._client.session, ) diff --git a/test/unit/utils.test.js b/test/unit/utils.test.ts similarity index 83% rename from test/unit/utils.test.js rename to test/unit/utils.test.ts index f5fc089d8..491ecd113 100644 --- a/test/unit/utils.test.js +++ b/test/unit/utils.test.ts @@ -1,16 +1,21 @@ import sinon from 'sinon' import Debug from 'debug' -import express from 'express' +import express, { Application } from 'express' import authFetch from '../../src/rest/authFetch' import { uuid, getEndpointUrl } from '../../src/utils' +import { Server } from 'http' const debug = Debug('StreamrClient::test::utils') +interface TestResponse { + test: string +} + describe('utils', () => { - let session - let expressApp - let server + let session: any + let expressApp: Application + let server: Server const baseUrl = 'http://127.0.0.1:30000' const testUrl = '/some-test-url' @@ -19,7 +24,7 @@ describe('utils', () => { session.options = {} expressApp = express() - function handle(req, res) { + function handle(req: any, res: any) { if (req.get('Authorization') !== 'Bearer session-token') { res.sendStatus(401) } else { @@ -29,7 +34,7 @@ describe('utils', () => { } } - expressApp.get(testUrl, (req, res) => handle(req, res)) + expressApp.get(testUrl, (req: any, res: any) => handle(req, res)) server = expressApp.listen(30000, () => { debug('Mock server started on port 30000\n') @@ -44,7 +49,7 @@ describe('utils', () => { describe('authFetch', () => { it('should return normally when valid session token is passed', async () => { session.getSessionToken = sinon.stub().resolves('session-token') - const res = await authFetch(baseUrl + testUrl, session) + const res = await authFetch(baseUrl + testUrl, session) expect(session.getSessionToken.calledOnce).toBeTruthy() expect(res.test).toBeTruthy() }) @@ -52,7 +57,7 @@ describe('utils', () => { it('should return 401 error when invalid session token is passed twice', async () => { session.getSessionToken = sinon.stub().resolves('invalid token') const onCaught = jest.fn() - await authFetch(baseUrl + testUrl, session).catch((err) => { + await authFetch(baseUrl + testUrl, session).catch((err) => { onCaught() expect(session.getSessionToken.calledTwice).toBeTruthy() expect(err.toString()).toMatch( @@ -68,7 +73,7 @@ describe('utils', () => { session.getSessionToken.onCall(0).resolves('expired-session-token') session.getSessionToken.onCall(1).resolves('session-token') - const res = await authFetch(baseUrl + testUrl, session) + const res = await authFetch(baseUrl + testUrl, session) expect(session.getSessionToken.calledTwice).toBeTruthy() expect(res.test).toBeTruthy() }) diff --git a/test/utils.ts b/test/utils.ts index 7d2e92ce9..f260ca07b 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -87,7 +87,7 @@ export function getWaitForStorage(client: StreamrClient, defaultOpts = {}) { } const start = Date.now() - let last + let last: any // eslint-disable-next-line no-constant-condition let found = false while (!found) { @@ -98,7 +98,7 @@ export function getWaitForStorage(client: StreamrClient, defaultOpts = {}) { duration }, { publishRequest, - last: last.map((l: any) => l.content), + last: last!.map((l: any) => l.content), }) const err: any = new Error(`timed out after ${duration}ms waiting for message`) err.publishRequest = publishRequest From d35841ca33e2b3f700de288ba46855cf5a0b8317 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 2 Mar 2021 13:52:10 +0200 Subject: [PATCH 505/517] Error handling of getStream/createStream/etc. (#208) createStream, getStream, getStreamByName and getOrCreateStream reject the promise if there is an API error. To support this functionality, a new errorCode field was added to AuthFetchError. Currently the ErrorCode enum lists the most typical API error codes: VALIDATION_ERROR and NOT_FOUND. Other codes can be added later, if needed. --- README.md | 2 +- src/rest/ErrorCode.ts | 20 +++++++++ src/rest/StreamEndpoints.ts | 55 ++++++++++-------------- src/rest/authFetch.ts | 9 ++-- test/integration/StreamEndpoints.test.js | 34 ++++++++++++--- test/utils.ts | 2 +- 6 files changed, 79 insertions(+), 43 deletions(-) create mode 100644 src/rest/ErrorCode.ts diff --git a/README.md b/README.md index 46be08f20..f2a088ead 100644 --- a/README.md +++ b/README.md @@ -303,7 +303,7 @@ All the below functions return a Promise which gets resolved with the result. | getStream(streamId) | Fetches a stream object from the API. | | listStreams(query) | Fetches an array of stream objects from the API. For the query params, consult the [API docs](https://api-explorer.streamr.com). | | getStreamByName(name) | Fetches a stream which exactly matches the given name. | -| createStream(properties) | Creates a stream with the given properties. For more information on the stream properties, consult the [API docs](https://api-explorer.streamr.com). | +| createStream(\[properties]) | Creates a stream with the given properties. For more information on the stream properties, consult the [API docs](https://api-explorer.streamr.com). | | getOrCreateStream(properties) | Gets a stream with the id or name given in `properties`, or creates it if one is not found. | | publish(streamId, message, timestamp, partitionKey) | Publishes a new message to the given stream. | diff --git a/src/rest/ErrorCode.ts b/src/rest/ErrorCode.ts new file mode 100644 index 000000000..f7f1b95a2 --- /dev/null +++ b/src/rest/ErrorCode.ts @@ -0,0 +1,20 @@ +export enum ErrorCode { + NOT_FOUND = 'NOT_FOUND', + VALIDATION_ERROR = 'VALIDATION_ERROR', + UNKNOWN = 'UNKNOWN' +} + +export const parseErrorCode = (body: string) => { + let json + try { + json = JSON.parse(body) + } catch (err) { + return ErrorCode.UNKNOWN + } + const code = json.code + const keys = Object.keys(ErrorCode) + if (keys.includes(code)) { + return code as ErrorCode + } + return ErrorCode.UNKNOWN +} diff --git a/src/rest/StreamEndpoints.ts b/src/rest/StreamEndpoints.ts index 01c1c5a6a..3960bdfaa 100644 --- a/src/rest/StreamEndpoints.ts +++ b/src/rest/StreamEndpoints.ts @@ -10,9 +10,10 @@ import Stream, { StreamOperation, StreamProperties } from '../stream' import StreamPart from '../stream/StreamPart' import { isKeyExchangeStream } from '../stream/KeyExchange' -import authFetch from './authFetch' +import authFetch, { AuthFetchError } from './authFetch' import { Todo } from '../types' import StreamrClient from '../StreamrClient' +import { ErrorCode } from './ErrorCode' // TODO change this import when streamr-client-protocol exports StreamMessage type or the enums types directly import { ContentType, EncryptionType, SignatureType, StreamMessageType } from 'streamr-client-protocol/dist/src/protocol/message_layer/StreamMessage' @@ -97,18 +98,11 @@ export class StreamEndpoints { } const url = getEndpointUrl(this.client.options.restUrl, 'streams', streamId) - try { - const json = await authFetch(url, this.client.session) - return new Stream(this.client, json) - } catch (e) { - if (e.response && e.response.status === 404) { - return undefined - } - throw e - } + const json = await authFetch(url, this.client.session) + return new Stream(this.client, json) } - async listStreams(query: StreamListQuery = {}) { + async listStreams(query: StreamListQuery = {}): Promise { this.client.debug('listStreams %o', { query, }) @@ -126,10 +120,10 @@ export class StreamEndpoints { // @ts-expect-error public: false, }) - return json[0] ? new Stream(this.client, json[0]) : undefined + return json[0] ? new Stream(this.client, json[0]) : Promise.reject(new AuthFetchError('', undefined, undefined, ErrorCode.NOT_FOUND)) } - async createStream(props: StreamProperties) { + async createStream(props?: StreamProperties) { this.client.debug('createStream %o', { props, }) @@ -142,34 +136,31 @@ export class StreamEndpoints { body: JSON.stringify(props), }, ) - return json ? new Stream(this.client, json) : undefined + return new Stream(this.client, json) } async getOrCreateStream(props: { id?: string, name?: string }) { this.client.debug('getOrCreateStream %o', { props, }) - let json: any - // Try looking up the stream by id or name, whichever is defined - if (props.id) { - json = await this.getStream(props.id) - } else if (props.name) { - json = await this.getStreamByName(props.name) - } - - // If not found, try creating the stream - if (!json) { - json = await this.createStream(props) - debug('Created stream: %s (%s)', props.name, json.id) + try { + if (props.id) { + const stream = await this.getStream(props.id) + return stream + } + const stream = await this.getStreamByName(props.name!) + return stream + } catch (err) { + const isNotFoundError = (err instanceof AuthFetchError) && (err.errorCode === ErrorCode.NOT_FOUND) + if (!isNotFoundError) { + throw err + } } - // If still nothing, throw - if (!json) { - throw new Error(`Unable to find or create stream: ${props.name || props.id}`) - } else { - return new Stream(this.client, json) - } + const stream = await this.createStream(props) + debug('Created stream: %s (%s)', props.name, stream.id) + return stream } async getStreamPublishers(streamId: string) { diff --git a/src/rest/authFetch.ts b/src/rest/authFetch.ts index 55083faf4..16a299bad 100644 --- a/src/rest/authFetch.ts +++ b/src/rest/authFetch.ts @@ -2,6 +2,7 @@ import fetch, { Response } from 'node-fetch' import Debug from 'debug' import { getVersionString } from '../utils' +import { ErrorCode, parseErrorCode } from './ErrorCode' import Session from '../Session' export const DEFAULT_HEADERS = { @@ -9,15 +10,17 @@ export const DEFAULT_HEADERS = { } export class AuthFetchError extends Error { - response: Response + response?: Response body?: any + errorCode?: ErrorCode - constructor(message: string, response: Response, body?: any) { + constructor(message: string, response?: Response, body?: any, errorCode?: ErrorCode) { // add leading space if there is a body set const bodyMessage = body ? ` ${(typeof body === 'string' ? body : JSON.stringify(body).slice(0, 1024))}...` : '' super(message + bodyMessage) this.response = response this.body = body + this.errorCode = errorCode if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor) @@ -75,6 +78,6 @@ export default async function authFetch(url: string, session?: return authFetch(url, session, options, true) } else { debug('%d %s – failed', id, url) - throw new AuthFetchError(`Request ${id} to ${url} returned with error code ${response.status}.`, response, body) + throw new AuthFetchError(`Request ${id} to ${url} returned with error code ${response.status}.`, response, body, parseErrorCode(body)) } } diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.js index 6be2ee3c9..0fd2ebf9e 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.js @@ -51,6 +51,10 @@ function TestStreamEndpoints(getName) { expect(stream.requireSignedData).toBe(true) expect(stream.requireEncryptedData).toBe(true) }) + + it('invalid id', () => { + return expect(() => client.createStream({ id: 'invalid.eth/foobar' })).rejects.toThrow() + }) }) describe('getStream', () => { @@ -62,13 +66,25 @@ function TestStreamEndpoints(getName) { it('get a non-existing Stream', async () => { const id = `${wallet.address}/StreamEndpoints-integration-nonexisting-${Date.now()}` - const stream = await client.getStream(id) - expect(stream).toBe(undefined) + return expect(() => client.getStream(id)).rejects.toThrow() + }) + }) + + describe('getStreamByName', () => { + it('get an existing Stream', async () => { + const stream = await client.createStream() + const existingStream = await client.getStreamByName(stream.name) + expect(existingStream.id).toEqual(stream.id) + }) + + it('get a non-existing Stream', async () => { + const name = `${wallet.address}/StreamEndpoints-integration-nonexisting-${Date.now()}` + return expect(() => client.getStreamByName(name)).rejects.toThrow() }) }) describe('getOrCreate', () => { - it('getOrCreate an existing Stream', async () => { + it('getOrCreate an existing Stream by name', async () => { const existingStream = await client.getOrCreateStream({ name: createdStream.name, }) @@ -76,12 +92,19 @@ function TestStreamEndpoints(getName) { expect(existingStream.name).toBe(createdStream.name) }) + it('getOrCreate an existing Stream by id', async () => { + const existingStream = await client.getOrCreateStream({ + id: createdStream.id, + }) + expect(existingStream.id).toBe(createdStream.id) + expect(existingStream.name).toBe(createdStream.name) + }) + it('getOrCreate a new Stream by name', async () => { const newName = uid('stream') const newStream = await client.getOrCreateStream({ name: newName, }) - expect(newStream.name).toEqual(newName) }) @@ -90,7 +113,6 @@ function TestStreamEndpoints(getName) { const newStream = await client.getOrCreateStream({ id: newId, }) - expect(newStream.id).toEqual(newId) }) }) @@ -201,7 +223,7 @@ function TestStreamEndpoints(getName) { describe('Stream deletion', () => { it('Stream.delete', async () => { await createdStream.delete() - expect(await client.getStream(createdStream.id)).toBe(undefined) + return expect(() => client.getStream(createdStream.id)).rejects.toThrow() }) }) diff --git a/test/utils.ts b/test/utils.ts index f260ca07b..3c068aba9 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -237,7 +237,7 @@ export function getPublishTestMessages(client: StreamrClient, defaultOpts = {}) export const createMockAddress = () => '0x000000000000000000000000000' + Date.now() -export const createClient = (providerSidechain: providers.JsonRpcProvider) => { +export const createClient = (providerSidechain?: providers.JsonRpcProvider) => { const wallet = new Wallet(`0x100000000000000000000000000000000000000012300000001${Date.now()}`, providerSidechain) return new StreamrClient({ ...config.clientOptions, From fda00679ca15ab969ca48376f2f15e4e7d75ca93 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Tue, 2 Mar 2021 15:02:46 +0200 Subject: [PATCH 506/517] DataUnion types (#209) Types for getAdminAddress, setAdminFee, executeWithdraw. After these modifications, and PR #207 all public methods of DataUnion are annotated with valid types. Remove deployTxReceipt field which was not used --- src/dataunion/Contracts.ts | 12 +++++------- src/dataunion/DataUnion.ts | 8 ++++---- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/src/dataunion/Contracts.ts b/src/dataunion/Contracts.ts index e190f44ba..dcb7bdd80 100644 --- a/src/dataunion/Contracts.ts +++ b/src/dataunion/Contracts.ts @@ -1,6 +1,6 @@ import { getCreate2Address, isAddress } from '@ethersproject/address' import { arrayify, hexZeroPad } from '@ethersproject/bytes' -import { Contract } from '@ethersproject/contracts' +import { Contract, ContractReceipt } from '@ethersproject/contracts' import { keccak256 } from '@ethersproject/keccak256' import { defaultAbiCoder } from '@ethersproject/abi' import { verifyMessage } from '@ethersproject/wallet' @@ -218,14 +218,14 @@ export class Contracts { return trAMB } - async payForSignatureTransport(tr: { events: any[] }, options: { pollingIntervalMs?: number, retryTimeoutMs?: number } = {}) { + async payForSignatureTransport(tr: ContractReceipt, options: { pollingIntervalMs?: number, retryTimeoutMs?: number } = {}) { const { pollingIntervalMs = 1000, retryTimeoutMs = 60000, } = options - log(`Got receipt, filtering UserRequestForSignature from ${tr.events.length} events...`) + log(`Got receipt, filtering UserRequestForSignature from ${tr.events!.length} events...`) // event UserRequestForSignature(bytes32 indexed messageId, bytes encodedData); - const sigEventArgsArray = tr.events.filter((e: Todo) => e.event === 'UserRequestForSignature').map((e: Todo) => e.args) + const sigEventArgsArray = tr.events!.filter((e: Todo) => e.event === 'UserRequestForSignature').map((e: Todo) => e.args) if (sigEventArgsArray.length < 1) { throw new Error("No UserRequestForSignature events emitted from withdraw transaction, can't transport withdraw to mainnet") } @@ -309,7 +309,7 @@ export class Contracts { duName, ethersOptions ) - const tr = await tx.wait(confirmations) + await tx.wait(confirmations) log(`Data Union "${duName}" (mainnet: ${duMainnetAddress}, sidechain: ${duSidechainAddress}) deployed to mainnet, waiting for side-chain...`) await until( @@ -320,8 +320,6 @@ export class Contracts { const dataUnion = new Contract(duMainnetAddress, dataUnionMainnetABI, mainnetWallet) // @ts-expect-error - dataUnion.deployTxReceipt = tr - // @ts-expect-error dataUnion.sidechain = new Contract(duSidechainAddress, dataUnionSidechainABI, sidechainProvider) return dataUnion diff --git a/src/dataunion/DataUnion.ts b/src/dataunion/DataUnion.ts index a499bb387..3fe0e304e 100644 --- a/src/dataunion/DataUnion.ts +++ b/src/dataunion/DataUnion.ts @@ -6,7 +6,7 @@ import { TransactionReceipt, TransactionResponse } from '@ethersproject/provider import debug from 'debug' import { Contracts } from './Contracts' import StreamrClient from '../StreamrClient' -import { EthereumAddress, Todo } from '../types' +import { EthereumAddress } from '../types' import { until, getEndpointUrl } from '../utils' import authFetch from '../rest/authFetch' @@ -296,7 +296,7 @@ export class DataUnion { return +adminFeeBN.toString() / 1e18 } - async getAdminAddress(): Promise { + async getAdminAddress(): Promise { const duMainnet = this.getContracts().getMainnetContractReadOnly(this.contractAddress) return duMainnet.owner() } @@ -446,7 +446,7 @@ export class DataUnion { /** * Admin: set admin fee (between 0.0 and 1.0) for the data union */ - async setAdminFee(newFeeFraction: number): Promise { + async setAdminFee(newFeeFraction: number): Promise { if (newFeeFraction < 0 || newFeeFraction > 1) { throw new Error('newFeeFraction argument must be a number between 0...1, got: ' + newFeeFraction) } @@ -539,7 +539,7 @@ export class DataUnion { // template for withdraw functions // client could be replaced with AMB (mainnet and sidechain) private async _executeWithdraw( - getWithdrawTxFunc: () => Promise, + getWithdrawTxFunc: () => Promise, recipientAddress: EthereumAddress, options: DataUnionWithdrawOptions = {} ): Promise { From 9b2805fdb32b90711deb327ce9681d97bf45de3d Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Wed, 3 Mar 2021 13:16:32 +0200 Subject: [PATCH 507/517] Fix connection.test.js (#211) Changed Connection.debug to optional parameter --- package-lock.json | 9 ++ package.json | 1 + src/{Connection.js => Connection.ts} | 102 +++++++++++------- src/StreamrClient.ts | 6 +- ...{Connection.test.js => Connection.test.ts} | 59 +++++----- 5 files changed, 109 insertions(+), 68 deletions(-) rename src/{Connection.js => Connection.ts} (90%) rename test/unit/{Connection.test.js => Connection.test.ts} (97%) diff --git a/package-lock.json b/package-lock.json index 19ba7d60c..aa1c75f20 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2601,6 +2601,15 @@ "integrity": "sha512-eQ9qFW/fhfGJF8WKHGEHZEyVWfZxrT+6CLIJGBcZPfxUh/+BnEj+UCGYMlr9qZuX/2AltsvwrGqp0LhEW8D0zQ==", "dev": true }, + "@types/ws": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.0.tgz", + "integrity": "sha512-Y29uQ3Uy+58bZrFLhX36hcI3Np37nqWE7ky5tjiDoy1GDZnIwVxS0CgF+s+1bXMzjKBFy+fqaRfb708iNzdinw==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/yargs": { "version": "15.0.13", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", diff --git a/package.json b/package.json index 8f8e0bd1b..c1a03e99f 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "@types/qs": "^6.9.5", "@types/sinon": "^9.0.10", "@types/uuid": "^8.3.0", + "@types/ws": "^7.4.0", "@typescript-eslint/eslint-plugin": "^4.15.1", "@typescript-eslint/parser": "^4.15.1", "babel-loader": "^8.2.2", diff --git a/src/Connection.js b/src/Connection.ts similarity index 90% rename from src/Connection.js rename to src/Connection.ts index ddff7ecde..f593a4620 100644 --- a/src/Connection.js +++ b/src/Connection.ts @@ -5,25 +5,31 @@ import Debug from 'debug' import WebSocket from 'ws' import { Scaffold, counterId, pLimitFn, pOne } from './utils' +import { Todo } from './types' -const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms)) +const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) // add global support for pretty millisecond formatting with %n +// @ts-expect-error Debug.formatters.n = (v) => Debug.humanize(v) export class ConnectionError extends Error { - constructor(err, ...args) { + reason?: Todo + + constructor(err: Todo, ...args: Todo[]) { if (err instanceof ConnectionError) { return err } if (err && err.stack) { const { message, stack } = err + // @ts-expect-error super(message, ...args) Object.assign(this, err) this.stack = stack this.reason = err } else { + // @ts-expect-error super(err, ...args) if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor) @@ -35,7 +41,7 @@ export class ConnectionError extends Error { const openSockets = new Set() const FORCE_CLOSED = Symbol('FORCE_CLOSED') -async function OpenWebSocket(url, opts, ...args) { +async function OpenWebSocket(url: string, opts: Todo, ...args: Todo[]) { return new Promise((resolve, reject) => { try { if (!url) { @@ -44,8 +50,9 @@ async function OpenWebSocket(url, opts, ...args) { throw err } + // @ts-expect-error const socket = process.browser ? new WebSocket(url) : new WebSocket(url, opts, ...args) - let error + let error: Todo Object.assign(socket, { id: counterId('ws'), binaryType: 'arraybuffer', @@ -57,13 +64,15 @@ async function OpenWebSocket(url, opts, ...args) { openSockets.delete(socket) reject(new ConnectionError(error || 'socket closed')) }, - onerror(event) { + onerror(event: Todo) { error = new ConnectionError(event.error || event) }, }) // attach debug + // @ts-expect-error socket.debug = opts.debug.extend(socket.id) + // @ts-expect-error socket.debug.color = opts.debug.color // use existing colour } catch (err) { reject(err) @@ -71,10 +80,10 @@ async function OpenWebSocket(url, opts, ...args) { }) } -async function CloseWebSocket(socket) { +async function CloseWebSocket(socket: Todo) { return new Promise((resolve, reject) => { if (!socket || socket.readyState === WebSocket.CLOSED) { - resolve() + resolve(undefined) return } @@ -82,6 +91,7 @@ async function CloseWebSocket(socket) { resolve(CloseWebSocket(socket)) ) + // @ts-expect-error if (socket.readyState === WebSocket.OPENING) { socket.addEventListener('error', waitThenClose) socket.addEventListener('open', waitThenClose) @@ -110,9 +120,9 @@ const STATE = { } /* eslint-disable no-underscore-dangle, no-param-reassign */ -function SocketConnector(connection) { - let next - let socket +function SocketConnector(connection: Todo) { + let next: Todo + let socket: Todo let startedConnecting = false let didCloseUnexpectedly = false @@ -120,7 +130,7 @@ function SocketConnector(connection) { didCloseUnexpectedly = true if (!next.pendingCount && !next.activeCount) { // if no pending actions run next & emit any errors - next().catch((err) => { + next().catch((err: Todo) => { connection.emit('error', err) }) } @@ -207,7 +217,7 @@ function SocketConnector(connection) { }, // attach message handler () => { - const onMessage = (messageEvent, ...args) => { + const onMessage = (messageEvent: Todo, ...args: Todo|[]) => { connection.emit('message', messageEvent, ...args) } socket.addEventListener('message', onMessage) @@ -275,12 +285,26 @@ const DEFAULT_MAX_RETRIES = 10 */ export default class Connection extends EventEmitter { + + _debug: Todo + options: Todo + retryCount: Todo + wantsState: Todo + connectionHandles: Todo + step: Todo + socket?: Todo + didDisableAutoConnect?: Todo + isWaiting?: Todo + _isReconnecting: Todo + _backoffTimeout: Todo + sendID: Todo + static getOpen() { return openSockets.size } static async closeOpen() { - return Promise.all([...openSockets].map(async (socket) => { + return Promise.all([...openSockets].map(async (socket: Todo) => { socket[FORCE_CLOSED] = true // eslint-disable-line no-param-reassign return CloseWebSocket(socket).catch((err) => { socket.debug(err) // ignore error @@ -288,9 +312,9 @@ export default class Connection extends EventEmitter { })) } - constructor(options = {}, client) { + constructor(options = {}, debug?: Debug.Debugger) { super() - this._debug = client.debug.extend(counterId(this.constructor.name)) + this._debug = (debug !== undefined) ? debug.extend(counterId(this.constructor.name)) : Debug(`StreamrClient::${counterId(this.constructor.name)}`) this.options = options this.options.autoConnect = !!this.options.autoConnect @@ -303,19 +327,20 @@ export default class Connection extends EventEmitter { this.backoffWait = pLimitFn(this.backoffWait.bind(this)) this.step = SocketConnector(this) this.debug = this.debug.bind(this) + // @ts-expect-error this.maybeConnect = pOne(this.maybeConnect.bind(this)) this.nextConnection = pOne(this.nextConnection.bind(this)) this.nextDisconnection = pOne(this.nextDisconnection.bind(this)) } - debug(...args) { + debug(...args: Todo[]) { if (this.socket) { return this.socket.debug(...args) } return this._debug(...args) } - emit(event, ...args) { + emit(event: Todo, ...args: Todo[]) { if (event === 'error') { let [err] = args const [, ...rest] = args @@ -342,7 +367,7 @@ export default class Connection extends EventEmitter { return result } - emitTransition(event, ...args) { + emitTransition(event: Todo, ...args: Todo[]) { const prevWantsState = this.wantsState if (prevWantsState === STATE.AUTO) { return this.emit(event, ...args) @@ -426,25 +451,25 @@ export default class Connection extends EventEmitter { this.isWaiting = true return new Promise((resolve, reject) => { - let onError - let onDone + let onError: Todo + let onDone: Todo const onConnected = () => { this.off('done', onDone) this.off('error', onError) this.off('_error', onError) - resolve() + resolve(undefined) } - onDone = (err) => { + onDone = (err: Todo) => { this.off('error', onError) this.off('_error', onError) this.off('connected', onConnected) if (err) { reject(err) } else { - resolve() + resolve(undefined) } } - onError = (err) => { + onError = (err: Todo) => { this.off('done', onDone) this.off('connected', onConnected) reject(err) @@ -504,7 +529,7 @@ export default class Connection extends EventEmitter { await this.step() } - async needsConnection(msg) { + async needsConnection(msg?: Todo) { await this.maybeConnect() if (!this.isConnected()) { const { autoConnect, autoDisconnect } = this.options @@ -550,12 +575,12 @@ export default class Connection extends EventEmitter { } return new Promise((resolve, reject) => { - let onError + let onError: Todo const onDisconnected = () => { this.off('error', onError) - resolve() + resolve(undefined) } - onError = (err) => { + onError = (err: Todo) => { this.off('disconnected', onDisconnected) reject(err) } @@ -576,7 +601,7 @@ export default class Connection extends EventEmitter { debug('waiting %n', timeout) this._backoffTimeout = setTimeout(() => { debug('waited %n', timeout) - resolve() + resolve(undefined) }, timeout) }) } @@ -585,7 +610,7 @@ export default class Connection extends EventEmitter { * Auto Connect/Disconnect counters. */ - async addHandle(id) { + async addHandle(id: Todo) { if ( this.connectionHandles.has(id) && this.isConnected() @@ -602,7 +627,7 @@ export default class Connection extends EventEmitter { * When no more handles and autoDisconnect is true, disconnect. */ - async removeHandle(id) { + async removeHandle(id: Todo) { const hadConnection = this.connectionHandles.has(id) this.connectionHandles.delete(id) if (hadConnection && this._couldAutoDisconnect()) { @@ -619,7 +644,7 @@ export default class Connection extends EventEmitter { ) } - async send(msg) { + async send(msg: Todo) { this.sendID = this.sendID + 1 || 1 const handle = `send${this.sendID}` this.debug('(%s) send()', this.getState()) @@ -638,7 +663,7 @@ export default class Connection extends EventEmitter { } } - async _send(msg) { + async _send(msg: Todo) { return new Promise((resolve, reject) => { this.debug('(%s) >> %o', this.getState(), msg) // promisify send @@ -646,11 +671,12 @@ export default class Connection extends EventEmitter { // send callback doesn't exist with browser websockets, just resolve /* istanbul ignore next */ this.emit('_send', msg) // for informational purposes + // @ts-expect-error if (process.browser) { this.socket.send(data) resolve(data) } else { - this.socket.send(data, (err) => { + this.socket.send(data, (err: Todo) => { /* istanbul ignore next */ if (err) { reject(new ConnectionError(err)) @@ -727,9 +753,10 @@ export default class Connection extends EventEmitter { onDisconnecting = () => {}, onDisconnected = () => {}, onDone = () => {}, + // @ts-expect-error onError, }) { - let onDoneHandler + let onDoneHandler: Todo const cleanUp = async () => { this .off('connecting', onConnecting) @@ -742,8 +769,10 @@ export default class Connection extends EventEmitter { } } - onDoneHandler = async (...args) => { + onDoneHandler = async (...args: Todo[]) => { + // @ts-expect-error cleanUp(...args) + // @ts-expect-error return onDone(...args) } @@ -762,4 +791,5 @@ export default class Connection extends EventEmitter { } } +// @ts-expect-error Connection.ConnectionError = ConnectionError diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index 3f0202ac4..74f368769 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -34,8 +34,8 @@ export { StreamrClientOptions } class StreamrConnection extends Connection { // TODO define args type when we convert Connection class to TypeScript - constructor(options: Todo, client: StreamrClient) { - super(options, client) + constructor(options: Todo, debug?: Debug.Debugger) { + super(options, debug) this.on('message', this.onConnectionMessage) } @@ -179,7 +179,7 @@ class StreamrClient extends EventEmitter { this.on('error', this._onError) // attach before creating sub-components incase they fire error events this.session = new Session(this, this.options.auth) - this.connection = connection || new StreamrConnection(this.options, this) + this.connection = connection || new StreamrConnection(this.options, this.debug) this.connection .on('connected', this.onConnectionConnected) diff --git a/test/unit/Connection.test.js b/test/unit/Connection.test.ts similarity index 97% rename from test/unit/Connection.test.js rename to test/unit/Connection.test.ts index 7020555c8..dcaf21e08 100644 --- a/test/unit/Connection.test.js +++ b/test/unit/Connection.test.ts @@ -1,35 +1,36 @@ -import { Server } from 'ws' +import { AddressInfo, Server } from 'ws' import { wait } from 'streamr-test-utils' import Debug from 'debug' import { describeRepeats } from '../utils' import Connection from '../../src/Connection' import { Defer } from '../../src/utils' +import { Todo } from '../../src/types' /* eslint-disable require-atomic-updates */ const debug = Debug('StreamrClient').extend('test') describeRepeats('Connection', () => { - let s - let onConnected - let onConnecting - let onDisconnecting - let onDisconnected - let onReconnecting - let onDone - let onError - let onMessage - let wss - let port - let errors + let s: Connection + let onConnected: Todo + let onConnecting: Todo + let onDisconnecting: Todo + let onDisconnected: Todo + let onReconnecting: Todo + let onDone: Todo + let onError: Todo + let onMessage: Todo + let wss: Server + let port: number + let errors: Todo let expectErrors = 0 // check no errors by default beforeAll((done) => { wss = new Server({ port: 0, }).once('listening', () => { - port = wss.address().port + port = (wss.address() as AddressInfo).port done() }) @@ -76,7 +77,7 @@ describeRepeats('Connection', () => { }) afterEach(async () => { - await wait() + await wait(0) // ensure no unexpected errors try { expect(errors).toHaveLength(expectErrors) @@ -287,7 +288,7 @@ describeRepeats('Connection', () => { await expect(async () => { await s.connect() }).rejects.toThrow() - done.resolve() + done.resolve(undefined) })) await expect(async () => { @@ -449,7 +450,7 @@ describeRepeats('Connection', () => { s.once('connecting', () => { // purposely unchained // eslint-disable-next-line promise/catch-or-return - wait().then(() => ( + wait(0).then(() => ( s.disconnect() )).then(resolve, reject) }) @@ -496,7 +497,7 @@ describeRepeats('Connection', () => { }) s.once('error', done.wrap(async (err) => { expect(err).toBe(error) - await wait() + await wait(0) expect(s.getState()).toBe('connected') })) await s.connect() @@ -777,7 +778,7 @@ describeRepeats('Connection', () => { expect(err).toBeTruthy() expect(onConnected).toHaveBeenCalledTimes(1) expect(s.getState()).toBe('disconnected') - await wait() + await wait(0) expect(onDone).toHaveBeenCalledTimes(1) }) @@ -791,7 +792,7 @@ describeRepeats('Connection', () => { )).rejects.toThrow('badurl') expect(onConnected).toHaveBeenCalledTimes(1) expect(s.getState()).toBe('disconnected') - await wait() + await wait(0) expect(onDone).toHaveBeenCalledTimes(1) }) @@ -812,7 +813,7 @@ describeRepeats('Connection', () => { } }) s.once('connected', () => { - done.resolve() + done.resolve(undefined) }) s.socket.close() await done @@ -850,7 +851,7 @@ describeRepeats('Connection', () => { expect(err).toBeTruthy() s.options.url = goodUrl await s.connect() - await wait() + await wait(0) expect(s.isReconnecting()).toBeFalsy() expect(s.getState()).toBe('connected') }) @@ -933,7 +934,7 @@ describeRepeats('Connection', () => { s.once('message', done.resolve) await s.send('test') - const { data } = await done + const { data }: any = await done expect(data).toEqual('test') }) @@ -949,7 +950,7 @@ describeRepeats('Connection', () => { s.connect() // no await await s.send('test') - const { data } = await done + const { data }: any = await done expect(data).toEqual('test') }) @@ -959,7 +960,7 @@ describeRepeats('Connection', () => { s.once('message', done.resolve) // no connect await s.send('test') - const { data } = await done + const { data }: any = await done expect(data).toEqual('test') }) @@ -969,7 +970,7 @@ describeRepeats('Connection', () => { s.once('message', done.resolve) s.socket.close() // will trigger reconnect await s.send('test') - const { data } = await done + const { data }: any = await done expect(data).toEqual('test') }) @@ -993,8 +994,8 @@ describeRepeats('Connection', () => { it('fails send if autoconnected but intentionally disconnected', async () => { s.enableAutoConnect() - const received = [] - s.on('message', ({ data } = {}) => { + const received: Todo[] = [] + s.on('message', ({ data }: any = {}) => { received.push(data) }) const nextMessage = Defer() @@ -1174,7 +1175,7 @@ describeRepeats('Connection', () => { await s.removeHandle(2) expect(s.getState()).toBe('connected') const t = s.removeHandle(1) - await wait() + await wait(0) await s.disconnect() // disconnect while auto-disconnecting await t expect(s.getState()).toBe('disconnected') From 9ccd610ecc296feaa7cf118b55eb7f4d94d3be94 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Wed, 3 Mar 2021 17:44:25 +0200 Subject: [PATCH 508/517] Stream reject on error refactor (#212) Better error handling of authFetch errors: added classes ValidationError and NotFoundError. Also smaller changes: - added missing id field to StreamPermission - strict type check of parameter in getOrCreateStream() --- src/rest/ErrorCode.ts | 20 -------- src/rest/StreamEndpoints.ts | 12 ++--- src/rest/authFetch.ts | 46 +++++++++++++++++-- src/stream/index.ts | 3 ++ ...points.test.js => StreamEndpoints.test.ts} | 34 +++++++------- 5 files changed, 67 insertions(+), 48 deletions(-) rename test/integration/{StreamEndpoints.test.js => StreamEndpoints.test.ts} (89%) diff --git a/src/rest/ErrorCode.ts b/src/rest/ErrorCode.ts index f7f1b95a2..e69de29bb 100644 --- a/src/rest/ErrorCode.ts +++ b/src/rest/ErrorCode.ts @@ -1,20 +0,0 @@ -export enum ErrorCode { - NOT_FOUND = 'NOT_FOUND', - VALIDATION_ERROR = 'VALIDATION_ERROR', - UNKNOWN = 'UNKNOWN' -} - -export const parseErrorCode = (body: string) => { - let json - try { - json = JSON.parse(body) - } catch (err) { - return ErrorCode.UNKNOWN - } - const code = json.code - const keys = Object.keys(ErrorCode) - if (keys.includes(code)) { - return code as ErrorCode - } - return ErrorCode.UNKNOWN -} diff --git a/src/rest/StreamEndpoints.ts b/src/rest/StreamEndpoints.ts index 3960bdfaa..3e4c1d3b3 100644 --- a/src/rest/StreamEndpoints.ts +++ b/src/rest/StreamEndpoints.ts @@ -10,10 +10,9 @@ import Stream, { StreamOperation, StreamProperties } from '../stream' import StreamPart from '../stream/StreamPart' import { isKeyExchangeStream } from '../stream/KeyExchange' -import authFetch, { AuthFetchError } from './authFetch' +import authFetch, { ErrorCode, NotFoundError } from './authFetch' import { Todo } from '../types' import StreamrClient from '../StreamrClient' -import { ErrorCode } from './ErrorCode' // TODO change this import when streamr-client-protocol exports StreamMessage type or the enums types directly import { ContentType, EncryptionType, SignatureType, StreamMessageType } from 'streamr-client-protocol/dist/src/protocol/message_layer/StreamMessage' @@ -120,7 +119,7 @@ export class StreamEndpoints { // @ts-expect-error public: false, }) - return json[0] ? new Stream(this.client, json[0]) : Promise.reject(new AuthFetchError('', undefined, undefined, ErrorCode.NOT_FOUND)) + return json[0] ? new Stream(this.client, json[0]) : Promise.reject(new NotFoundError('Stream: name=' + name)) } async createStream(props?: StreamProperties) { @@ -139,7 +138,7 @@ export class StreamEndpoints { return new Stream(this.client, json) } - async getOrCreateStream(props: { id?: string, name?: string }) { + async getOrCreateStream(props: { id: string, name?: never } | { id?: never, name: string }) { this.client.debug('getOrCreateStream %o', { props, }) @@ -151,9 +150,8 @@ export class StreamEndpoints { } const stream = await this.getStreamByName(props.name!) return stream - } catch (err) { - const isNotFoundError = (err instanceof AuthFetchError) && (err.errorCode === ErrorCode.NOT_FOUND) - if (!isNotFoundError) { + } catch (err: any) { + if (err.errorCode !== ErrorCode.NOT_FOUND) { throw err } } diff --git a/src/rest/authFetch.ts b/src/rest/authFetch.ts index 16a299bad..62f9f99f0 100644 --- a/src/rest/authFetch.ts +++ b/src/rest/authFetch.ts @@ -2,9 +2,14 @@ import fetch, { Response } from 'node-fetch' import Debug from 'debug' import { getVersionString } from '../utils' -import { ErrorCode, parseErrorCode } from './ErrorCode' import Session from '../Session' +export enum ErrorCode { + NOT_FOUND = 'NOT_FOUND', + VALIDATION_ERROR = 'VALIDATION_ERROR', + UNKNOWN = 'UNKNOWN' +} + export const DEFAULT_HEADERS = { 'Streamr-Client': `streamr-client-javascript/${getVersionString()}`, } @@ -12,15 +17,16 @@ export const DEFAULT_HEADERS = { export class AuthFetchError extends Error { response?: Response body?: any - errorCode?: ErrorCode + errorCode: ErrorCode constructor(message: string, response?: Response, body?: any, errorCode?: ErrorCode) { + const typePrefix = errorCode ? errorCode + ': ' : '' // add leading space if there is a body set const bodyMessage = body ? ` ${(typeof body === 'string' ? body : JSON.stringify(body).slice(0, 1024))}...` : '' - super(message + bodyMessage) + super(typePrefix + message + bodyMessage) this.response = response this.body = body - this.errorCode = errorCode + this.errorCode = errorCode || ErrorCode.UNKNOWN if (Error.captureStackTrace) { Error.captureStackTrace(this, this.constructor) @@ -28,6 +34,34 @@ export class AuthFetchError extends Error { } } +export class ValidationError extends AuthFetchError { + constructor(message: string, response?: Response, body?: any) { + super(message, response, body, ErrorCode.VALIDATION_ERROR) + } +} + +export class NotFoundError extends AuthFetchError { + constructor(message: string, response?: Response, body?: any) { + super(message, response, body, ErrorCode.NOT_FOUND) + } +} + +const ERROR_TYPES = new Map() +ERROR_TYPES.set(ErrorCode.VALIDATION_ERROR, ValidationError) +ERROR_TYPES.set(ErrorCode.NOT_FOUND, NotFoundError) +ERROR_TYPES.set(ErrorCode.UNKNOWN, AuthFetchError) + +const parseErrorCode = (body: string) => { + let json + try { + json = JSON.parse(body) + } catch (err) { + return ErrorCode.UNKNOWN + } + const { code } = json + return code in ErrorCode ? code : ErrorCode.UNKNOWN +} + const debug = Debug('StreamrClient:utils:authfetch') // TODO: could use the debug instance from the client? (e.g. client.debug.extend('authFetch')) let ID = 0 @@ -78,6 +112,8 @@ export default async function authFetch(url: string, session?: return authFetch(url, session, options, true) } else { debug('%d %s – failed', id, url) - throw new AuthFetchError(`Request ${id} to ${url} returned with error code ${response.status}.`, response, body, parseErrorCode(body)) + const errorCode = parseErrorCode(body) + const ErrorClass = ERROR_TYPES.get(errorCode)! + throw new ErrorClass(`Request ${id} to ${url} returned with error code ${response.status}.`, response, body, errorCode) } } diff --git a/src/stream/index.ts b/src/stream/index.ts index b0b6a0595..3be723063 100644 --- a/src/stream/index.ts +++ b/src/stream/index.ts @@ -6,6 +6,7 @@ import StreamrClient from '../StreamrClient' import { Todo } from '../types' interface StreamPermisionBase { + id: number operation: StreamOperation } @@ -72,6 +73,8 @@ export default class Stream { // TODO add field definitions for all fields // @ts-expect-error id: string + // @ts-expect-error + name: string config: { fields: Field[]; } = { fields: [] } diff --git a/test/integration/StreamEndpoints.test.js b/test/integration/StreamEndpoints.test.ts similarity index 89% rename from test/integration/StreamEndpoints.test.js rename to test/integration/StreamEndpoints.test.ts index 0fd2ebf9e..ccd617f6f 100644 --- a/test/integration/StreamEndpoints.test.js +++ b/test/integration/StreamEndpoints.test.ts @@ -1,4 +1,6 @@ -import { ethers } from 'ethers' +import { ethers, Wallet } from 'ethers' +import { NotFoundError, ValidationError } from '../../src/rest/authFetch' +import Stream, { StreamOperation } from '../../src/stream' import StreamrClient from '../../src/StreamrClient' import { uid } from '../utils' @@ -9,17 +11,17 @@ import config from './config' * These tests should be run in sequential order! */ -function TestStreamEndpoints(getName) { - let client - let wallet - let createdStream +function TestStreamEndpoints(getName: () => string) { + let client: StreamrClient + let wallet: Wallet + let createdStream: Stream const createClient = (opts = {}) => new StreamrClient({ ...config.clientOptions, autoConnect: false, autoDisconnect: false, ...opts, - }) + } as any) beforeAll(() => { wallet = ethers.Wallet.createRandom() @@ -53,7 +55,7 @@ function TestStreamEndpoints(getName) { }) it('invalid id', () => { - return expect(() => client.createStream({ id: 'invalid.eth/foobar' })).rejects.toThrow() + return expect(() => client.createStream({ id: 'invalid.eth/foobar' })).rejects.toThrow(ValidationError) }) }) @@ -66,7 +68,7 @@ function TestStreamEndpoints(getName) { it('get a non-existing Stream', async () => { const id = `${wallet.address}/StreamEndpoints-integration-nonexisting-${Date.now()}` - return expect(() => client.getStream(id)).rejects.toThrow() + return expect(() => client.getStream(id)).rejects.toThrow(NotFoundError) }) }) @@ -79,7 +81,7 @@ function TestStreamEndpoints(getName) { it('get a non-existing Stream', async () => { const name = `${wallet.address}/StreamEndpoints-integration-nonexisting-${Date.now()}` - return expect(() => client.getStreamByName(name)).rejects.toThrow() + return expect(() => client.getStreamByName(name)).rejects.toThrow(NotFoundError) }) }) @@ -205,25 +207,25 @@ function TestStreamEndpoints(getName) { }) it('Stream.hasPermission', async () => { - expect(await createdStream.hasPermission('stream_share', wallet.address)).toBeTruthy() + expect(await createdStream.hasPermission(StreamOperation.STREAM_SHARE, wallet.address)).toBeTruthy() }) it('Stream.grantPermission', async () => { - await createdStream.grantPermission('stream_subscribe', null) // public read - expect(await createdStream.hasPermission('stream_subscribe', null)).toBeTruthy() + await createdStream.grantPermission(StreamOperation.STREAM_SUBSCRIBE, undefined) // public read + expect(await createdStream.hasPermission(StreamOperation.STREAM_SUBSCRIBE, undefined)).toBeTruthy() }) it('Stream.revokePermission', async () => { - const publicRead = await createdStream.hasPermission('stream_subscribe', null) - await createdStream.revokePermission(publicRead.id) - expect(!(await createdStream.hasPermission('stream_subscribe', null))).toBeTruthy() + const publicRead = await createdStream.hasPermission(StreamOperation.STREAM_SUBSCRIBE, undefined) + await createdStream.revokePermission(publicRead!.id) + expect(!(await createdStream.hasPermission(StreamOperation.STREAM_SUBSCRIBE, undefined))).toBeTruthy() }) }) describe('Stream deletion', () => { it('Stream.delete', async () => { await createdStream.delete() - return expect(() => client.getStream(createdStream.id)).rejects.toThrow() + return expect(() => client.getStream(createdStream.id)).rejects.toThrow(NotFoundError) }) }) From 1db5b30be0db2113aa200a3a6c86ef89cdb1371e Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Thu, 11 Mar 2021 17:00:10 +0200 Subject: [PATCH 509/517] Export public types, update DU config (#210) Export parameters/return types for objects which are publicly available to the JS-client users. Updated getDataUnionMainnetAddress and getDataUnionSidechainAddress in Contracts.ts to support new DataUnion addresses. BREAKING CHANGE: - moved DataUnion related StreamrClient options into a new dataUnion block: factoryMainnetAddress, - factorySidechainAddress, minimumWithdrawTokenWei - renamed payForSignatureTransport to freeWithdraw and reversed the value - new required options in that block: templateMainnetAddress, templateSidechainAddress --- .babel.config.js | 2 +- .eslintignore | 2 + .github/workflows/nodejs.yml | 37 +++++++-- .gitignore | 3 + README.md | 18 +++-- copy-package.js | 15 ++++ examples/node/node-example-produce.js | 2 +- examples/node/node-example-subscribe.js | 2 +- package-lock.json | 80 +++++++++---------- package.json | 42 ++++++---- src/Config.ts | 50 +++++++----- src/Session.ts | 8 +- src/StreamrClient.ts | 27 +++---- src/dataunion/Contracts.ts | 63 ++++++++------- src/dataunion/DataUnion.ts | 14 ++-- src/index-commonjs.js | 5 ++ src/index-esm.mjs | 6 ++ src/index.ts | 24 ++++++ src/publish/Encrypt.ts | 4 +- src/rest/ErrorCode.ts | 0 src/rest/LoginEndpoints.ts | 3 +- src/rest/StreamEndpoints.ts | 13 +-- src/rest/authFetch.ts | 1 + src/stream/Encryption.js | 1 + src/stream/StorageNode.ts | 6 +- src/stream/index.ts | 6 +- src/subscribe/index.js | 3 +- src/utils/index.ts | 2 +- test/benchmarks/publish.js | 4 +- test/benchmarks/subscribe.js | 2 +- test/exports/package.json | 22 +++++ test/exports/tests/commonjs.js | 14 ++++ test/exports/tests/esm.mjs | 16 ++++ test/exports/tests/typescript.ts | 19 +++++ test/exports/tsconfig.json | 15 ++++ test/exports/webpack.config.js | 47 +++++++++++ test/flakey/EnvStressTest.test.js | 2 +- test/integration/Encryption.test.js | 2 +- test/integration/GapFill.test.js | 2 +- test/integration/LoginEndpoints.test.js | 2 +- test/integration/MultipleClients.test.js | 2 +- test/integration/ResendReconnect.test.js | 2 +- test/integration/Resends.test.js | 2 +- test/integration/Sequencing.test.js | 2 +- test/integration/Session.test.js | 2 +- test/integration/Stream.test.js | 2 +- .../integration/StreamConnectionState.test.js | 2 +- test/integration/StreamEndpoints.test.ts | 4 +- test/integration/StreamrClient.test.js | 2 +- test/integration/Subscriber.test.js | 2 +- test/integration/SubscriberResends.test.js | 2 +- test/integration/Subscription.test.js | 2 +- test/integration/Validation.test.js | 2 +- test/integration/authFetch.test.js | 2 +- test/integration/config.js | 8 +- test/integration/dataunion/adminFee.test.ts | 2 +- test/integration/dataunion/calculate.test.ts | 2 +- test/integration/dataunion/deploy.test.ts | 2 +- test/integration/dataunion/member.test.ts | 2 +- test/integration/dataunion/signature.test.ts | 2 +- test/integration/dataunion/stats.test.ts | 2 +- test/integration/dataunion/withdraw.test.ts | 2 +- test/legacy/MessageCreationUtil.test.js | 2 +- test/unit/Session.test.js | 2 +- test/unit/Stream.test.js | 2 +- test/unit/StubbedStreamrClient.js | 4 +- test/utils.ts | 2 +- tsconfig.json | 58 +++++++------- tsconfig.node.json | 10 +-- webpack.config.js | 15 +++- 70 files changed, 503 insertions(+), 230 deletions(-) create mode 100644 copy-package.js create mode 100644 src/index-commonjs.js create mode 100644 src/index-esm.mjs create mode 100644 src/index.ts delete mode 100644 src/rest/ErrorCode.ts create mode 100644 test/exports/package.json create mode 100644 test/exports/tests/commonjs.js create mode 100644 test/exports/tests/esm.mjs create mode 100644 test/exports/tests/typescript.ts create mode 100644 test/exports/tsconfig.json create mode 100644 test/exports/webpack.config.js diff --git a/.babel.config.js b/.babel.config.js index 422df09ab..5716478ee 100644 --- a/.babel.config.js +++ b/.babel.config.js @@ -24,7 +24,7 @@ module.exports = { plugins: [ "add-module-exports", ['@babel/plugin-transform-runtime', { - corejs: false, + corejs: 3, helpers: true, regenerator: false }], diff --git a/.eslintignore b/.eslintignore index 86f9d8952..20d5177ca 100644 --- a/.eslintignore +++ b/.eslintignore @@ -6,3 +6,5 @@ test/legacy/** src/shim/** test/unit/StubbedStreamrClient.js streamr-docker-dev/** +vendor/** +test/exports/** diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 13104dfd5..4176bd4cf 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -48,8 +48,6 @@ jobs: run: npm ci - name: npm run eslint run: npm run eslint - - name: test-types - run: npm run test-types test: name: Test Unit using Node ${{ matrix.node-version }} @@ -107,7 +105,7 @@ jobs: - name: npm ci run: npm ci - name: Start Streamr Docker Stack - uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.2 + uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.3 with: services-to-start: "mysql redis engine-and-editor cassandra parity-node0 parity-sidechain-node0 bridge broker-node-storage-1 nginx smtp" - name: Run Test @@ -129,7 +127,7 @@ jobs: - name: npm ci run: npm ci - name: Start Streamr Docker Stack - uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.2 + uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.3 with: services-to-start: "mysql redis engine-and-editor cassandra parity-node0 parity-sidechain-node0 bridge broker-node-storage-1 nginx smtp" - uses: nick-invision/retry@v2 @@ -140,6 +138,26 @@ jobs: retry_on: error command: npm run test-flakey || echo "::warning::Flakey Tests Failed" + test-exports: + name: Test Exports using Node 14.x + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: "14.x" + - uses: actions/download-artifact@v2 + with: + name: build + path: dist + - name: npm ci + run: npm ci + - name: test-types + run: npm run test-types + - name: npm run test-exports + run: npm run test-exports + browser: name: Test Browser using Node 14.x runs-on: ubuntu-latest @@ -156,7 +174,7 @@ jobs: - name: npm ci run: npm ci - name: Start Streamr Docker Stack - uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.2 + uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.3 with: services-to-start: "mysql redis engine-and-editor cassandra parity-node0 parity-sidechain-node0 bridge broker-node-storage-1 nginx smtp" @@ -186,7 +204,7 @@ jobs: name: build path: dist - name: Start Streamr Docker Stack - uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.2 + uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.3 with: services-to-start: "mysql redis engine-and-editor cassandra parity-node0 parity-sidechain-node0 bridge broker-node-storage-1 nginx smtp" - name: npm ci @@ -233,17 +251,20 @@ jobs: name: build path: dist - name: Start Streamr Docker Stack - uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.2 + uses: streamr-dev/streamr-docker-dev-action@v1.0.0-alpha.3 with: services-to-start: "mysql redis engine-and-editor cassandra parity-node0 parity-sidechain-node0 bridge broker-node-storage-1 nginx smtp" - name: npm ci run: npm ci - name: npm link - run: npm link + run: cd dist && npm link - uses: actions/checkout@v2 with: repository: streamr-dev/streamr-client-testing path: streamr-client-testing + - uses: actions/setup-java@v1 + with: + java-version: '8' - name: setup-client-testing working-directory: streamr-client-testing run: | diff --git a/.gitignore b/.gitignore index 409d3b4a3..c55ede7b5 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,6 @@ examples/webpack/dist/* reports tests_outputbenchmarks.txt tests_output +test/exports/dist +test/exports/package-lock.json +vendor diff --git a/README.md b/README.md index f2a088ead..b91bff58e 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,10 @@ const client = new StreamrClient({ }) ``` -When using Node.js remember to require the library with: +When using Node.js remember to import the library with: ```js -const StreamrClient = require('streamr-client') +import { StreamrClient } from 'streamr-client'; ``` ### Subscribing to real-time events in a stream @@ -329,11 +329,15 @@ This library provides functions for working with Data Unions. To get a DataUnion TODO: All `options`-parameters should be documented (see TypeScript interfaces for the definitions) These DataUnion-specific options are used from `StreamrClient` options: -| Property | Default | Description | -| :----------------------- | :----------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | -| tokenAddress | 0x0Cf0Ee637
88A0849fE52
97F3407f701
E122cC023 | Token used by the DU | -| factoryMainnetAddress | TODO | Data Union factory that creates a new Data Union | -| minimumWithdrawTokenWei | 1000000 | Threshold value set in AMB configs, smallest token amount that can pass over the bridge | +| Property | Default | Description | +| :---------------------------------- | :----------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------- | +| tokenAddress | 0x0Cf0Ee637
88A0849fE52
97F3407f701
E122cC023 | Token used by the DU | +| dataUnion.minimumWithdrawTokenWei | 1000000 | Threshold value set in AMB configs, smallest token amount that can pass over the bridge | +| dataUnion.freeWithdraw | false | true = someone else pays for the gas when transporting the withdraw tx to mainnet; false = client does the transport as self-service and pays the mainnet gas costs | +| dataUnion.factoryMainnetAddress | TODO | Data Union factory that creates a new Data Union | +| dataUnion.factorySidechainAddress | TODO | | +| dataUnion.templateMainnetAddress | TODO | | +| dataUnion.templateSidechainAddress | TODO | | ### Admin Functions diff --git a/copy-package.js b/copy-package.js new file mode 100644 index 000000000..43b27d153 --- /dev/null +++ b/copy-package.js @@ -0,0 +1,15 @@ +const fs = require('fs') +// eslint-disable-next-line +const pkg = Object.assign({}, require('./package.json')) + +delete pkg.scripts + +try { + fs.mkdirSync('./dist/') +} catch (err) { + if (err.code !== 'EEXIST') { + throw err + } +} + +fs.writeFileSync('./dist/package.json', JSON.stringify(pkg, null, 2)) diff --git a/examples/node/node-example-produce.js b/examples/node/node-example-produce.js index 25a6bb95f..310711fe8 100644 --- a/examples/node/node-example-produce.js +++ b/examples/node/node-example-produce.js @@ -1,4 +1,4 @@ -const StreamrClient = require('streamr-client') +import { StreamrClient } from 'streamr-client'; // Create the client and supply either an API key or an Ethereum private key to authenticate const client = new StreamrClient({ diff --git a/examples/node/node-example-subscribe.js b/examples/node/node-example-subscribe.js index a38d06ff2..05f0ce2a7 100644 --- a/examples/node/node-example-subscribe.js +++ b/examples/node/node-example-subscribe.js @@ -1,4 +1,4 @@ -const StreamrClient = require('streamr-client') +import { StreamrClient } from 'streamr-client'; // Create the client and supply either an API key or an Ethereum private key to authenticate const client = new StreamrClient({ diff --git a/package-lock.json b/package-lock.json index aa1c75f20..d93fdbc59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2636,13 +2636,13 @@ } }, "@typescript-eslint/eslint-plugin": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.15.2.tgz", - "integrity": "sha512-uiQQeu9tWl3f1+oK0yoAv9lt/KXO24iafxgQTkIYO/kitruILGx3uH+QtIAHqxFV+yIsdnJH+alel9KuE3J15Q==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.17.0.tgz", + "integrity": "sha512-/fKFDcoHg8oNan39IKFOb5WmV7oWhQe1K6CDaAVfJaNWEhmfqlA24g+u1lqU5bMH7zuNasfMId4LaYWC5ijRLw==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.15.2", - "@typescript-eslint/scope-manager": "4.15.2", + "@typescript-eslint/experimental-utils": "4.17.0", + "@typescript-eslint/scope-manager": "4.17.0", "debug": "^4.1.1", "functional-red-black-tree": "^1.0.1", "lodash": "^4.17.15", @@ -2663,55 +2663,55 @@ } }, "@typescript-eslint/experimental-utils": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.15.2.tgz", - "integrity": "sha512-Fxoshw8+R5X3/Vmqwsjc8nRO/7iTysRtDqx6rlfLZ7HbT8TZhPeQqbPjTyk2RheH3L8afumecTQnUc9EeXxohQ==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.17.0.tgz", + "integrity": "sha512-ZR2NIUbnIBj+LGqCFGQ9yk2EBQrpVVFOh9/Kd0Lm6gLpSAcCuLLe5lUCibKGCqyH9HPwYC0GIJce2O1i8VYmWA==", "dev": true, "requires": { "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.15.2", - "@typescript-eslint/types": "4.15.2", - "@typescript-eslint/typescript-estree": "4.15.2", + "@typescript-eslint/scope-manager": "4.17.0", + "@typescript-eslint/types": "4.17.0", + "@typescript-eslint/typescript-estree": "4.17.0", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" } }, "@typescript-eslint/parser": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.15.2.tgz", - "integrity": "sha512-SHeF8xbsC6z2FKXsaTb1tBCf0QZsjJ94H6Bo51Y1aVEZ4XAefaw5ZAilMoDPlGghe+qtq7XdTiDlGfVTOmvA+Q==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.17.0.tgz", + "integrity": "sha512-KYdksiZQ0N1t+6qpnl6JeK9ycCFprS9xBAiIrw4gSphqONt8wydBw4BXJi3C11ywZmyHulvMaLjWsxDjUSDwAw==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.15.2", - "@typescript-eslint/types": "4.15.2", - "@typescript-eslint/typescript-estree": "4.15.2", + "@typescript-eslint/scope-manager": "4.17.0", + "@typescript-eslint/types": "4.17.0", + "@typescript-eslint/typescript-estree": "4.17.0", "debug": "^4.1.1" } }, "@typescript-eslint/scope-manager": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.2.tgz", - "integrity": "sha512-Zm0tf/MSKuX6aeJmuXexgdVyxT9/oJJhaCkijv0DvJVT3ui4zY6XYd6iwIo/8GEZGy43cd7w1rFMiCLHbRzAPQ==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.17.0.tgz", + "integrity": "sha512-OJ+CeTliuW+UZ9qgULrnGpPQ1bhrZNFpfT/Bc0pzNeyZwMik7/ykJ0JHnQ7krHanFN9wcnPK89pwn84cRUmYjw==", "dev": true, "requires": { - "@typescript-eslint/types": "4.15.2", - "@typescript-eslint/visitor-keys": "4.15.2" + "@typescript-eslint/types": "4.17.0", + "@typescript-eslint/visitor-keys": "4.17.0" } }, "@typescript-eslint/types": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.2.tgz", - "integrity": "sha512-r7lW7HFkAarfUylJ2tKndyO9njwSyoy6cpfDKWPX6/ctZA+QyaYscAHXVAfJqtnY6aaTwDYrOhp+ginlbc7HfQ==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.17.0.tgz", + "integrity": "sha512-RN5z8qYpJ+kXwnLlyzZkiJwfW2AY458Bf8WqllkondQIcN2ZxQowAToGSd9BlAUZDB5Ea8I6mqL2quGYCLT+2g==", "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.2.tgz", - "integrity": "sha512-cGR8C2g5SPtHTQvAymEODeqx90pJHadWsgTtx6GbnTWKqsg7yp6Eaya9nFzUd4KrKhxdYTTFBiYeTPQaz/l8bw==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.17.0.tgz", + "integrity": "sha512-lRhSFIZKUEPPWpWfwuZBH9trYIEJSI0vYsrxbvVvNyIUDoKWaklOAelsSkeh3E2VBSZiNe9BZ4E5tYBZbUczVQ==", "dev": true, "requires": { - "@typescript-eslint/types": "4.15.2", - "@typescript-eslint/visitor-keys": "4.15.2", + "@typescript-eslint/types": "4.17.0", + "@typescript-eslint/visitor-keys": "4.17.0", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", @@ -2731,12 +2731,12 @@ } }, "@typescript-eslint/visitor-keys": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.2.tgz", - "integrity": "sha512-TME1VgSb7wTwgENN5KVj4Nqg25hP8DisXxNBojM4Nn31rYaNDIocNm5cmjOFfh42n7NVERxWrDFoETO/76ePyg==", + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.17.0.tgz", + "integrity": "sha512-WfuMN8mm5SSqXuAr9NM+fItJ0SVVphobWYkWOwQ1odsfC014Vdxk/92t4JwS1Q6fCA/ABfCKpa3AVtpUKTNKGQ==", "dev": true, "requires": { - "@typescript-eslint/types": "4.15.2", + "@typescript-eslint/types": "4.17.0", "eslint-visitor-keys": "^2.0.0" } }, @@ -13088,9 +13088,9 @@ "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==" }, "tsutils": { - "version": "3.20.0", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.20.0.tgz", - "integrity": "sha512-RYbuQuvkhuqVeXweWT3tJLKOEJ/UUw9GjNEZGWdrLLlM+611o1gwLHBpxoFJKKl25fLprp2eVthtKs5JOrNeXg==", + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", "dev": true, "requires": { "tslib": "^1.8.1" @@ -13160,9 +13160,9 @@ } }, "typescript": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.1.5.tgz", - "integrity": "sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.2.3.tgz", + "integrity": "sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==", "dev": true }, "unicode-canonical-property-names-ecmascript": { diff --git a/package.json b/package.json index c1a03e99f..c3a37e03f 100644 --- a/package.json +++ b/package.json @@ -6,18 +6,29 @@ "type": "git", "url": "git://github.com/streamr-dev/streamr-client.git" }, - "types": "dist/types/src/StreamrClient.d.ts", - "main": "dist/node/src/StreamrClient.js", - "browser": "dist/streamr-client.web.min.js", - "directories": { - "example": "examples", - "test": "test" + "types": "./types/src/index.d.ts", + "main": "./src/index-commonjs.js", + "browser": "./streamr-client.web.js", + "exports": { + "browser": "./streamr-client.web.js", + "default": { + "import": "./src/index-esm.mjs", + "require": "./src/index-commonjs.js" + } }, "scripts": { - "build": "rm -rf dist; NODE_ENV=production webpack --mode=production --progress && npm run build-node && npm run build:types", - "build-node": "tsc --incremental --project ./tsconfig.node.json", - "watch-node": "tsc --incremental --watch --project ./tsconfig.node.json", - "build:types": "tsc --incremental --emitDeclarationOnly", + "build": "rm -rf dist; rm -rf vendor; npm run bootstrap-dist && npm run build-browser && npm run build-node && npm run build:types", + "postinstall": "npm run vendor", + "build-node": "npm run bootstrap-dist && tsc --incremental --project ./tsconfig.node.json", + "postbuild-node": "npm run copy-package", + "build-browser": "npm run bootstrap-dist && NODE_ENV=production webpack --mode=production --progress ", + "watch-browser": "npm run bootstrap-dist && webpack --progress --watch", + "watch-node": "npm run bootstrap-dist && tsc --incremental --watch --project ./tsconfig.node.json", + "bootstrap-dist": "npm run vendor && npm run copy-package && npm run fix-esm", + "fix-esm": "mkdir -p dist/src/; cp -Rf src/index-esm.mjs dist/src/index-esm.mjs", + "vendor": "mkdir -p vendor/quick-lru; cp -n node_modules/quick-lru/index.d.ts vendor/quick-lru; cp -n node_modules/quick-lru/index.js vendor/quick-lru; true", + "copy-package": "mkdir -p dist/; node copy-package.js; cp -f package-lock.json dist; true", + "build:types": "tsc --incremental --emitDeclarationOnly --stripInternal", "benchmarks": "node test/benchmarks/publish.js && node test/benchmarks/subscribe.js", "prebuild-benchmark": "npm run build -- --config-name=node-lib", "build-benchmark": "npm run benchmarks", @@ -29,9 +40,10 @@ "eslint": "eslint --cache-location=node_modules/.cache/.eslintcache/ '*/**/*.{js,ts}'", "test": "jest --detectOpenHandles", "test-unit": "jest test/unit --detectOpenHandles", - "test-types": "tsc --noEmit --incremental --project ./tsconfig.test.json", + "test-types": "(cd test/exports && npm run link) && tsc --noEmit --incremental --project ./tsconfig.test.json", "coverage": "jest --coverage", "test-integration": "jest --forceExit test/integration", + "test-exports": "cd test/exports && npm run link && npm test", "test-integration-no-resend": "jest --forceExit --testTimeout=10000 --testPathIgnorePatterns='resend|Resend' --testNamePattern='^((?!(resend|Resend|resent|Resent|gap|Gap)).)*$' test/integration/*.test.js", "test-integration-resend": "jest --forceExit --testTimeout=15000 --testNamePattern='(resend|Resend|resent|Resent)' test/integration/*.test.js", "test-integration-dataunions": "jest --forceExit --testTimeout=15000 --runInBand test/integration/dataunion", @@ -39,7 +51,7 @@ "test-browser": "node ./test/browser/server.js & node node_modules/nightwatch/bin/nightwatch ./test/browser/browser.js && pkill -f server.js", "install-example": "cd examples/webpack && npm ci", "build-example": "cd examples/webpack && npm run build-with-parent", - "clear-cache": "rm -rf node_modules/.cache; rm -rf .cache; rm -rf dist; jest --clearCache;" + "clear-cache": "rm -rf node_modules/.cache; rm -rf .cache; rm -rf dist; rm -rf vendor; jest --clearCache;" }, "engines": { "node": ">= 12" @@ -67,8 +79,8 @@ "@types/sinon": "^9.0.10", "@types/uuid": "^8.3.0", "@types/ws": "^7.4.0", - "@typescript-eslint/eslint-plugin": "^4.15.1", - "@typescript-eslint/parser": "^4.15.1", + "@typescript-eslint/eslint-plugin": "^4.17.0", + "@typescript-eslint/parser": "^4.17.0", "babel-loader": "^8.2.2", "babel-plugin-add-module-exports": "^1.0.4", "babel-plugin-transform-class-properties": "^6.24.1", @@ -96,7 +108,7 @@ "terser-webpack-plugin": "^5.1.1", "ts-jest": "^26.5.1", "ts-loader": "^8.0.17", - "typescript": "^4.1.5", + "typescript": "^4.2.3", "util": "^0.12.3", "webpack": "^5.23.0", "webpack-bundle-analyzer": "^4.4.0", diff --git a/src/Config.ts b/src/Config.ts index 54d903dc9..03bd9c89d 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -4,13 +4,13 @@ import { ExternalProvider, JsonRpcFetchFunc } from '@ethersproject/providers' import { BigNumber } from '@ethersproject/bignumber' import { getVersionString } from './utils' import { ConnectionInfo } from '@ethersproject/web' -import { Todo } from './types' +import { EthereumAddress, Todo } from './types' export type EthereumConfig = ExternalProvider|JsonRpcFetchFunc -export type StreamrClientOptions = { +export type StrictStreamrClientOptions = { auth: { - privateKey?: string + privateKey?: EthereumAddress ethereum?: EthereumConfig apiKey?: string username?: string @@ -18,7 +18,7 @@ export type StreamrClientOptions = { } url: string restUrl: string - streamrNodeAddress: string + streamrNodeAddress: EthereumAddress autoConnect: boolean autoDisconnect: boolean orderMessages: boolean @@ -33,23 +33,29 @@ export type StreamrClientOptions = { keyExchange: Todo mainnet?: ConnectionInfo|string sidechain?: ConnectionInfo|string - dataUnion?: string - tokenAddress: string, - minimumWithdrawTokenWei?: BigNumber|number|string - factoryMainnetAddress: string - factorySidechainAddress: string - payForSignatureTransport: boolean + tokenAddress: EthereumAddress, + dataUnion: { + minimumWithdrawTokenWei: BigNumber|number|string + freeWithdraw: boolean + factoryMainnetAddress: EthereumAddress + factorySidechainAddress: EthereumAddress + templateMainnetAddress: EthereumAddress + templateSidechainAddress: EthereumAddress + }, cache: { maxSize: number, maxAge: number } } +export type StreamrClientOptions = Partial & { dataUnion: Partial}> + const { ControlMessage } = ControlLayer const { StreamMessage } = MessageLayer -export default function ClientConfig(opts: Partial = {}) { - const defaults: StreamrClientOptions = { +/** @internal */ +export default function ClientConfig(opts: StreamrClientOptions = {}) { + const defaults: StrictStreamrClientOptions = { // Authentication: identity used by this StreamrClient instance auth: {}, // can contain member privateKey or (window.)ethereum @@ -77,21 +83,29 @@ export default function ClientConfig(opts: Partial = {}) { // Ethereum and Data Union related options // For ethers.js provider params, see https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#provider mainnet: undefined, // Default to ethers.js default provider settings - sidechain: undefined, // TODO: add our default public service sidechain node, also find good PoA params below + sidechain: 'https://rpc.xdaichain.com/', tokenAddress: '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', - minimumWithdrawTokenWei: '1000000', // Threshold value set in AMB configs, smallest token amount to pass over the bridge - factoryMainnetAddress: 'TODO', // TODO // Data Union factory that creates a new Data Union - factorySidechainAddress: 'TODO', - payForSignatureTransport: true, // someone must pay for transporting the withdraw tx to mainnet, either us or bridge operator + dataUnion: { + minimumWithdrawTokenWei: '1000000', // Threshold value set in AMB configs, smallest token amount to pass over the bridge + freeWithdraw: false, // if someone else pays for the gas when transporting the withdraw tx to mainnet; otherwise the client does the transport as self-service and pays the mainnet gas costs + factoryMainnetAddress: '0x7d55f9981d4E10A193314E001b96f72FCc901e40', + factorySidechainAddress: '0x1b55587Beea0b5Bc96Bb2ADa56bD692870522e9f', + templateMainnetAddress: '0x5FE790E3751dd775Cb92e9086Acd34a2adeB8C7b', + templateSidechainAddress: '0xf1E9d6E254BeA3f0129018AcA1A50AEcb7D528be', + }, cache: { maxSize: 10000, maxAge: 30 * 60 * 1000, // 30 minutes } } - const options: StreamrClientOptions = { + const options: StrictStreamrClientOptions = { ...defaults, ...opts, + dataUnion: { + ...defaults.dataUnion, + ...opts.dataUnion + }, cache: { ...opts.cache, ...defaults.cache, diff --git a/src/Session.ts b/src/Session.ts index c3cd2397f..ef349071b 100644 --- a/src/Session.ts +++ b/src/Session.ts @@ -1,7 +1,8 @@ import EventEmitter from 'eventemitter3' import { Wallet } from '@ethersproject/wallet' import { ExternalProvider, JsonRpcFetchFunc, Web3Provider } from '@ethersproject/providers' -import StreamrClient from './StreamrClient' +import { StreamrClient } from './StreamrClient' +import { EthereumAddress } from './types' enum State { LOGGING_OUT = 'logging out', @@ -11,7 +12,7 @@ enum State { } export interface SessionOptions { - privateKey?: string + privateKey?: EthereumAddress ethereum?: ExternalProvider|JsonRpcFetchFunc apiKey?: string username?: string @@ -20,10 +21,11 @@ export interface SessionOptions { unauthenticated?: boolean } -export interface TokenObject { +interface TokenObject { token: string } +/** @internal */ export default class Session extends EventEmitter { _client: StreamrClient diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index 74f368769..bcd105fcf 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -4,14 +4,14 @@ import Debug from 'debug' import { counterId, uuid, CacheAsyncFn } from './utils' import { validateOptions } from './stream/utils' -import Config, { StreamrClientOptions } from './Config' +import Config, { StreamrClientOptions, StrictStreamrClientOptions } from './Config' import StreamrEthereum from './Ethereum' import Session from './Session' import Connection, { ConnectionError } from './Connection' import Publisher from './publish' -import Subscriber from './subscribe' +import { Subscriber } from './subscribe' import { getUserId } from './user' -import { Todo, MaybeAsync } from './types' +import { Todo, MaybeAsync, EthereumAddress } from './types' import { StreamEndpoints } from './rest/StreamEndpoints' import { LoginEndpoints } from './rest/LoginEndpoints' import { DataUnion, DataUnionDeployOptions } from './dataunion/DataUnion' @@ -26,8 +26,6 @@ interface MessageEvent { data: any } -export { StreamrClientOptions } - /** * Wrap connection message events with message parsing. */ @@ -139,13 +137,14 @@ function Plugin(targetInstance: any, srcInstance: any) { } // these are mixed in via Plugin function above -interface StreamrClient extends StreamEndpoints, LoginEndpoints {} +export interface StreamrClient extends StreamEndpoints, LoginEndpoints {} // eslint-disable-next-line no-redeclare -class StreamrClient extends EventEmitter { +export class StreamrClient extends EventEmitter { id: string debug: Debug.Debugger - options: StreamrClientOptions + options: StrictStreamrClientOptions + /** @internal */ session: Session connection: StreamrConnection publisher: Todo @@ -155,7 +154,7 @@ class StreamrClient extends EventEmitter { streamEndpoints: StreamEndpoints loginEndpoints: LoginEndpoints - constructor(options: Partial = {}, connection?: StreamrConnection) { + constructor(options: StreamrClientOptions = {}, connection?: StreamrConnection) { super() this.id = counterId(`${this.constructor.name}:${uid}`) this.debug = Debug(this.id) @@ -379,7 +378,7 @@ class StreamrClient extends EventEmitter { /** * Get token balance in "wei" (10^-18 parts) for given address */ - async getTokenBalance(address: string): Promise { + async getTokenBalance(address: EthereumAddress): Promise { const { tokenAddress } = this.options if (!tokenAddress) { throw new Error('StreamrClient has no tokenAddress configuration.') @@ -399,7 +398,7 @@ class StreamrClient extends EventEmitter { return token.balanceOf(addr) } - getDataUnion(contractAddress: string) { + getDataUnion(contractAddress: EthereumAddress) { return DataUnion._fromContractAddress(contractAddress, this) // eslint-disable-line no-underscore-dangle } @@ -407,7 +406,7 @@ class StreamrClient extends EventEmitter { return DataUnion._deploy(options, this) // eslint-disable-line no-underscore-dangle } - _getDataUnionFromName({ dataUnionName, deployerAddress }: { dataUnionName: string, deployerAddress: string}) { + _getDataUnionFromName({ dataUnionName, deployerAddress }: { dataUnionName: string, deployerAddress: EthereumAddress}) { return DataUnion._fromName({ // eslint-disable-line no-underscore-dangle dataUnionName, deployerAddress @@ -418,7 +417,3 @@ class StreamrClient extends EventEmitter { return StreamrEthereum.generateEthereumAccount() } } - -export default StreamrClient - -module.exports = StreamrClient diff --git a/src/dataunion/Contracts.ts b/src/dataunion/Contracts.ts index dcb7bdd80..f4f65f3c6 100644 --- a/src/dataunion/Contracts.ts +++ b/src/dataunion/Contracts.ts @@ -10,21 +10,25 @@ import { dataUnionMainnetABI, dataUnionSidechainABI, factoryMainnetABI, mainnetA import { until } from '../utils' import { BigNumber } from '@ethersproject/bignumber' import StreamrEthereum from '../Ethereum' -import StreamrClient from '../StreamrClient' +import { StreamrClient } from '../StreamrClient' const log = debug('StreamrClient::DataUnion') export class Contracts { ethereum: StreamrEthereum - factoryMainnetAddress: string - factorySidechainAddress: string + factoryMainnetAddress: EthereumAddress + factorySidechainAddress: EthereumAddress + templateMainnetAddress: EthereumAddress + templateSidechainAddress: EthereumAddress cachedSidechainAmb?: Todo constructor(client: StreamrClient) { this.ethereum = client.ethereum - this.factoryMainnetAddress = client.options.factoryMainnetAddress - this.factorySidechainAddress = client.options.factorySidechainAddress + this.factoryMainnetAddress = client.options.dataUnion.factoryMainnetAddress + this.factorySidechainAddress = client.options.dataUnion.factorySidechainAddress + this.templateMainnetAddress = client.options.dataUnion.templateMainnetAddress + this.templateSidechainAddress = client.options.dataUnion.templateSidechainAddress } async fetchDataUnionMainnetAddress( @@ -37,11 +41,15 @@ export class Contracts { } getDataUnionMainnetAddress(dataUnionName: string, deployerAddress: EthereumAddress) { - if (!this.factoryMainnetAddress) { - throw new Error('StreamrClient has no factoryMainnetAddress configuration.') + if (!isAddress(this.factoryMainnetAddress)) { + throw new Error('StreamrClient factoryMainnetAddress configuration is ' + (this.factoryMainnetAddress ? 'not a valid Ethereum address' : 'missing')) + } + + if (!isAddress(this.templateMainnetAddress)) { + throw new Error('StreamrClient templateMainnetAddress configuration is ' + (this.templateMainnetAddress ? 'not a valid Ethereum address' : 'missing')) } - // NOTE! this must be updated when DU sidechain smartcontract changes: keccak256(CloneLib.cloneBytecode(data_union_mainnet_template)); - const codeHash = '0x50a78bac973bdccfc8415d7d9cfd62898b8f7cf6e9b3a15e7d75c0cb820529eb' + // This magic hex comes from https://github.com/streamr-dev/data-union-solidity/blob/master/contracts/CloneLib.sol#L19 + const codeHash = keccak256(`0x3d602d80600a3d3981f3363d3d373d3d3d363d73${this.templateMainnetAddress.slice(2)}5af43d82803e903d91602b57fd5bf3`) const salt = keccak256(defaultAbiCoder.encode(['string', 'address'], [dataUnionName, deployerAddress])) return getCreate2Address(this.factoryMainnetAddress, salt, codeHash) } @@ -53,11 +61,15 @@ export class Contracts { } getDataUnionSidechainAddress(mainnetAddress: EthereumAddress) { - if (!this.factorySidechainAddress) { - throw new Error('StreamrClient has no factorySidechainAddress configuration.') + if (!isAddress(this.factorySidechainAddress)) { + throw new Error('StreamrClient factorySidechainAddress configuration is ' + (this.factorySidechainAddress ? 'not a valid Ethereum address' : 'missing')) } - // NOTE! this must be updated when DU sidechain smartcontract changes: keccak256(CloneLib.cloneBytecode(data_union_sidechain_template)) - const codeHash = '0x040cf686e25c97f74a23a4bf01c29dd77e260c4b694f5611017ce9713f58de83' + + if (!isAddress(this.templateSidechainAddress)) { + throw new Error('StreamrClient templateSidechainAddress configuration is ' + (this.templateSidechainAddress ? 'not a valid Ethereum address' : 'missing')) + } + // This magic hex comes from https://github.com/streamr-dev/data-union-solidity/blob/master/contracts/CloneLib.sol#L19 + const codeHash = keccak256(`0x3d602d80600a3d3981f3363d3d373d3d3d363d73${this.templateSidechainAddress.slice(2)}5af43d82803e903d91602b57fd5bf3`) return getCreate2Address(this.factorySidechainAddress, hexZeroPad(mainnetAddress, 32), codeHash) } @@ -95,11 +107,8 @@ export class Contracts { async getSidechainAmb() { if (!this.cachedSidechainAmb) { const getAmbPromise = async () => { - const mainnetProvider = this.ethereum.getMainnetProvider() - const factoryMainnet = new Contract(this.factoryMainnetAddress, factoryMainnetABI, mainnetProvider) const sidechainProvider = this.ethereum.getSidechainProvider() - const factorySidechainAddress = await factoryMainnet.data_union_sidechain_factory() // TODO use getDataUnionSidechainAddress() - const factorySidechain = new Contract(factorySidechainAddress, [{ + const factorySidechain = new Contract(this.factorySidechainAddress, [{ name: 'amb', inputs: [], outputs: [{ type: 'address' }], @@ -137,7 +146,7 @@ export class Contracts { } // move signatures from sidechain to mainnet - async transportSignatures(messageHash: string) { + async transportSignaturesForMessage(messageHash: string) { const sidechainAmb = await this.getSidechainAmb() const message = await sidechainAmb.message(messageHash) const messageId = '0x' + message.substr(2, 64) @@ -171,7 +180,7 @@ export class Contracts { const alreadyProcessed = await mainnetAmb.relayedMessages(messageId) if (alreadyProcessed) { log(`WARNING: Tried to transport signatures but they have already been transported (Message ${messageId} has already been processed)`) - log('This could happen if payForSignatureTransport=true, but bridge operator also pays for signatures, and got there before your client') + log('This could happen if freeWithdraw=false (attempt self-service), but bridge actually paid before your client') return null } @@ -218,7 +227,7 @@ export class Contracts { return trAMB } - async payForSignatureTransport(tr: ContractReceipt, options: { pollingIntervalMs?: number, retryTimeoutMs?: number } = {}) { + async transportSignaturesForTransaction(tr: ContractReceipt, options: { pollingIntervalMs?: number, retryTimeoutMs?: number } = {}) { const { pollingIntervalMs = 1000, retryTimeoutMs = 60000, @@ -229,6 +238,7 @@ export class Contracts { if (sigEventArgsArray.length < 1) { throw new Error("No UserRequestForSignature events emitted from withdraw transaction, can't transport withdraw to mainnet") } + /* eslint-disable no-await-in-loop */ // eslint-disable-next-line no-restricted-syntax for (const eventArgs of sigEventArgsArray) { @@ -242,17 +252,16 @@ export class Contracts { const mainnetAmb = await this.getMainnetAmb() const alreadySent = await mainnetAmb.messageCallStatus(messageId) const failAddress = await mainnetAmb.failedMessageSender(messageId) - if (alreadySent || failAddress !== '0x0000000000000000000000000000000000000000') { // zero address means no failed messages + + // zero address means no failed messages + if (alreadySent || failAddress !== '0x0000000000000000000000000000000000000000') { log(`WARNING: Mainnet bridge has already processed withdraw messageId=${messageId}`) - log([ - 'This could happen if payForSignatureTransport=true, but bridge operator also pays for', - 'signatures, and got there before your client', - ].join(' ')) + log('This could happen if freeWithdraw=false (attempt self-service), but bridge actually paid before your client') continue } log(`Transporting signatures for hash=${messageHash}`) - await this.transportSignatures(messageHash) + await this.transportSignaturesForMessage(messageHash) } /* eslint-enable no-await-in-loop */ } @@ -294,7 +303,7 @@ export class Contracts { } if (await mainnetProvider.getCode(this.factoryMainnetAddress) === '0x') { - throw new Error(`Data union factory contract not found at ${this.factoryMainnetAddress}, check StreamrClient.options.factoryMainnetAddress!`) + throw new Error(`Data union factory contract not found at ${this.factoryMainnetAddress}, check StreamrClient.options.dataUnion.factoryMainnetAddress!`) } const factoryMainnet = new Contract(this.factoryMainnetAddress!, factoryMainnetABI, mainnetWallet) diff --git a/src/dataunion/DataUnion.ts b/src/dataunion/DataUnion.ts index 3fe0e304e..2c92d2b77 100644 --- a/src/dataunion/DataUnion.ts +++ b/src/dataunion/DataUnion.ts @@ -5,7 +5,7 @@ import { Contract } from '@ethersproject/contracts' import { TransactionReceipt, TransactionResponse } from '@ethersproject/providers' import debug from 'debug' import { Contracts } from './Contracts' -import StreamrClient from '../StreamrClient' +import { StreamrClient } from '../StreamrClient' import { EthereumAddress } from '../types' import { until, getEndpointUrl } from '../utils' import authFetch from '../rest/authFetch' @@ -35,7 +35,7 @@ export interface JoinResponse { export interface DataUnionWithdrawOptions { pollingIntervalMs?: number retryTimeoutMs?: number - payForSignatureTransport?: boolean + freeWithdraw?: boolean } export interface DataUnionMemberListModificationOptions { @@ -153,9 +153,9 @@ export class DataUnion { throw new Error(`${address} has nothing to withdraw in (sidechain) data union ${duSidechain.address}`) } - if (this.client.options.minimumWithdrawTokenWei && withdrawable.lt(this.client.options.minimumWithdrawTokenWei)) { + if (this.client.options.dataUnion.minimumWithdrawTokenWei && withdrawable.lt(this.client.options.dataUnion.minimumWithdrawTokenWei)) { throw new Error(`${address} has only ${withdrawable} to withdraw in ` - + `(sidechain) data union ${duSidechain.address} (min: ${this.client.options.minimumWithdrawTokenWei})`) + + `(sidechain) data union ${duSidechain.address} (min: ${this.client.options.dataUnion.minimumWithdrawTokenWei})`) } return duSidechain.withdrawAll(address, true) // sendToMainnet=true } @@ -546,14 +546,14 @@ export class DataUnion { const { pollingIntervalMs = 1000, retryTimeoutMs = 60000, - payForSignatureTransport = this.client.options.payForSignatureTransport + freeWithdraw = this.client.options.dataUnion.freeWithdraw }: any = options const getBalanceFunc = () => this.client.getTokenBalance(recipientAddress) const balanceBefore = await getBalanceFunc() const tx = await getWithdrawTxFunc() const tr = await tx.wait() - if (payForSignatureTransport) { - await this.getContracts().payForSignatureTransport(tr, options) + if (!freeWithdraw) { + await this.getContracts().transportSignaturesForTransaction(tr, options) } log(`Waiting for balance ${balanceBefore.toString()} to change`) await until(async () => !(await getBalanceFunc()).eq(balanceBefore), retryTimeoutMs, pollingIntervalMs) diff --git a/src/index-commonjs.js b/src/index-commonjs.js new file mode 100644 index 000000000..50b0515ef --- /dev/null +++ b/src/index-commonjs.js @@ -0,0 +1,5 @@ +const Client = require('./index') + +// required to get require('streamr-client') instead of require('streamr-client').default +module.exports = Client.default +Object.assign(Client.default, Client) diff --git a/src/index-esm.mjs b/src/index-esm.mjs new file mode 100644 index 000000000..35e094d45 --- /dev/null +++ b/src/index-esm.mjs @@ -0,0 +1,6 @@ +import StreamrClient from './index.js' +// required to get import { DataUnion } from 'streamr-client' to work +export * from './index.js' +// required to get import StreamrClient from 'streamr-client' to work +export default StreamrClient.default +// note this file is manually copied as-is into dist/src since we don't want tsc to compile it to commonjs diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 000000000..bb4b4c70d --- /dev/null +++ b/src/index.ts @@ -0,0 +1,24 @@ +import { StreamrClient } from './StreamrClient' + +export * from './StreamrClient' +export * from './Config' +export * from './stream' +export * from './stream/Encryption' +export * from './stream/StreamPart' +export * from './stream/StorageNode' +export * from './subscribe' +export * from './rest/LoginEndpoints' +export * from './rest/StreamEndpoints' +export * from './dataunion/DataUnion' +export * from './rest/authFetch' +export * from './types' + +// TODO should export these to support StreamMessageAsObject: export { StreamMessageType, ContentType, EncryptionType, SignatureType } from 'streamr-client-protocol/dist/src/protocol/message_layer/StreamMessage' +export { BigNumber } from '@ethersproject/bignumber' +export { ConnectionInfo } from '@ethersproject/web' +export { Contract } from '@ethersproject/contracts' +export { TransactionReceipt, TransactionResponse } from '@ethersproject/providers' + +export default StreamrClient + +// Note awful export wrappers in index-commonjs.js & index-esm.mjs diff --git a/src/publish/Encrypt.ts b/src/publish/Encrypt.ts index 0dbde22d4..b35e265b4 100644 --- a/src/publish/Encrypt.ts +++ b/src/publish/Encrypt.ts @@ -1,8 +1,8 @@ import { MessageLayer } from 'streamr-client-protocol' import EncryptionUtil from '../stream/Encryption' -import type Stream from '../stream' -import type StreamrClient from '../StreamrClient' +import { Stream } from '../stream' +import { StreamrClient } from '../StreamrClient' import { PublisherKeyExhange } from '../stream/KeyExchange' const { StreamMessage } = MessageLayer diff --git a/src/rest/ErrorCode.ts b/src/rest/ErrorCode.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/rest/LoginEndpoints.ts b/src/rest/LoginEndpoints.ts index 4a70bf49c..56e38f89d 100644 --- a/src/rest/LoginEndpoints.ts +++ b/src/rest/LoginEndpoints.ts @@ -1,4 +1,4 @@ -import StreamrClient from '../StreamrClient' +import { StreamrClient } from '../StreamrClient' import { getEndpointUrl } from '../utils' import authFetch, { AuthFetchError } from './authFetch' @@ -25,6 +25,7 @@ async function getSessionToken(url: string, props: any) { ) } +/** TODO the class should be annotated with at-internal, but adding the annotation hides the methods */ export class LoginEndpoints { client: StreamrClient diff --git a/src/rest/StreamEndpoints.ts b/src/rest/StreamEndpoints.ts index 3e4c1d3b3..a1b9426f3 100644 --- a/src/rest/StreamEndpoints.ts +++ b/src/rest/StreamEndpoints.ts @@ -6,13 +6,13 @@ import debugFactory from 'debug' import { getEndpointUrl } from '../utils' import { validateOptions } from '../stream/utils' -import Stream, { StreamOperation, StreamProperties } from '../stream' +import { Stream, StreamOperation, StreamProperties } from '../stream' import StreamPart from '../stream/StreamPart' import { isKeyExchangeStream } from '../stream/KeyExchange' import authFetch, { ErrorCode, NotFoundError } from './authFetch' -import { Todo } from '../types' -import StreamrClient from '../StreamrClient' +import { EthereumAddress, Todo } from '../types' +import { StreamrClient } from '../StreamrClient' // TODO change this import when streamr-client-protocol exports StreamMessage type or the enums types directly import { ContentType, EncryptionType, SignatureType, StreamMessageType } from 'streamr-client-protocol/dist/src/protocol/message_layer/StreamMessage' @@ -76,6 +76,7 @@ function getKeepAliveAgentForUrl(url: string) { throw new Error(`Unknown protocol in URL: ${url}`) } +/** TODO the class should be annotated with at-internal, but adding the annotation hides the methods */ export class StreamEndpoints { client: StreamrClient @@ -122,7 +123,7 @@ export class StreamEndpoints { return json[0] ? new Stream(this.client, json[0]) : Promise.reject(new NotFoundError('Stream: name=' + name)) } - async createStream(props?: StreamProperties) { + async createStream(props?: Partial) { this.client.debug('createStream %o', { props, }) @@ -236,11 +237,11 @@ export class StreamEndpoints { + `?${qs.stringify({ count })}` ) - const json = await authFetch(url, this.client.session) + const json = await authFetch(url, this.client.session) return json } - async getStreamPartsByStorageNode(address: string) { + async getStreamPartsByStorageNode(address: EthereumAddress) { type ItemType = { id: string, partitions: number} const json = await authFetch(getEndpointUrl(this.client.options.restUrl, 'storageNodes', address, 'streams'), this.client.session) let result: StreamPart[] = [] diff --git a/src/rest/authFetch.ts b/src/rest/authFetch.ts index 62f9f99f0..f38acb6a2 100644 --- a/src/rest/authFetch.ts +++ b/src/rest/authFetch.ts @@ -66,6 +66,7 @@ const debug = Debug('StreamrClient:utils:authfetch') // TODO: could use the debu let ID = 0 +/** @internal */ export default async function authFetch(url: string, session?: Session, opts?: any, requireNewToken = false): Promise { ID += 1 const timeStart = Date.now() diff --git a/src/stream/Encryption.js b/src/stream/Encryption.js index f682f0c58..d3b2771f1 100644 --- a/src/stream/Encryption.js +++ b/src/stream/Encryption.js @@ -246,6 +246,7 @@ class EncryptionUtilBase { } } +/** @internal */ export default class EncryptionUtil extends EncryptionUtilBase { /** * Creates a new instance + waits for ready. diff --git a/src/stream/StorageNode.ts b/src/stream/StorageNode.ts index 83d223561..2e18d5767 100644 --- a/src/stream/StorageNode.ts +++ b/src/stream/StorageNode.ts @@ -1,6 +1,8 @@ +import { EthereumAddress } from '../types' + export default class StorageNode { - _address: string - constructor(address: string) { + _address: EthereumAddress + constructor(address: EthereumAddress) { this._address = address } diff --git a/src/stream/index.ts b/src/stream/index.ts index 3be723063..9a7496a6c 100644 --- a/src/stream/index.ts +++ b/src/stream/index.ts @@ -2,7 +2,7 @@ import { getEndpointUrl } from '../utils' import authFetch from '../rest/authFetch' import StorageNode from './StorageNode' -import StreamrClient from '../StreamrClient' +import { StreamrClient } from '../StreamrClient' import { Todo } from '../types' interface StreamPermisionBase { @@ -45,7 +45,7 @@ export interface StreamProperties { const VALID_FIELD_TYPES = ['number', 'string', 'boolean', 'list', 'map'] as const -type Field = { +export type Field = { name: string; type: typeof VALID_FIELD_TYPES[number]; } @@ -69,7 +69,7 @@ function getFieldType(value: any): (Field['type'] | undefined) { } } -export default class Stream { +export class Stream { // TODO add field definitions for all fields // @ts-expect-error id: string diff --git a/src/subscribe/index.js b/src/subscribe/index.js index 93cab50e5..591e51c32 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.js @@ -464,8 +464,7 @@ class Subscriptions { /** * Top-level user-facing interface for creating/destroying subscriptions. */ - -export default class Subscriber { +export class Subscriber { constructor(client) { this.client = client this.subscriptions = new Subscriptions(client) diff --git a/src/utils/index.ts b/src/utils/index.ts index 9d4e06c46..a0788f679 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -3,13 +3,13 @@ import EventEmitter from 'events' import { v4 as uuidv4 } from 'uuid' import uniqueId from 'lodash.uniqueid' -import LRU from 'quick-lru' import pMemoize from 'p-memoize' import pLimit from 'p-limit' import mem from 'mem' import { L, F } from 'ts-toolbelt' import pkg from '../../package.json' +import LRU from '../../vendor/quick-lru' import { MaybeAsync } from '../types' import AggregatedError from './AggregatedError' diff --git a/test/benchmarks/publish.js b/test/benchmarks/publish.js index 95b5c3906..a98a158da 100644 --- a/test/benchmarks/publish.js +++ b/test/benchmarks/publish.js @@ -3,9 +3,11 @@ const { format } = require('util') const { Benchmark } = require('benchmark') // eslint-disable-next-line import/no-unresolved -const StreamrClient = require('../..') +const StreamrClient = require('../../dist') const config = require('../integration/config') +console.log('StreamrClient', { StreamrClient }) + /* eslint-disable no-console */ let count = 100000 // pedantic: use large initial number so payload size is similar diff --git a/test/benchmarks/subscribe.js b/test/benchmarks/subscribe.js index 171380169..77c8a658a 100644 --- a/test/benchmarks/subscribe.js +++ b/test/benchmarks/subscribe.js @@ -3,7 +3,7 @@ const { format } = require('util') const { Benchmark } = require('benchmark') // eslint-disable-next-line import/no-unresolved -const StreamrClient = require('../..') +const StreamrClient = require('../../dist') const config = require('../integration/config') /* eslint-disable no-console */ diff --git a/test/exports/package.json b/test/exports/package.json new file mode 100644 index 000000000..fa3878cd1 --- /dev/null +++ b/test/exports/package.json @@ -0,0 +1,22 @@ +{ + "name": "test-streamr-exports", + "version": "1.0.0", + "description": "", + "main": "commonjs.js", + "private": true, + "scripts": { + "pretest": "rm -Rf dist", + "test": "npm run test-commonjs && npm run test-esm && npm run test-ts && npm run webpack", + "build-ts": "tsc --project ./tsconfig.json", + "pretest-ts": "npm run build-ts", + "test-ts": "node dist/typescript.js", + "test-esm": "node tests/esm.mjs", + "test-commonjs": "node tests/commonjs.js", + "webpack": "../../node_modules/.bin/webpack --progress", + "link": "mkdir -p node_modules && ln -fs ../../../dist/ node_modules/streamr-client" + }, + "author": "Tim Oxley ", + "license": "ISC", + "dependencies": { + } +} diff --git a/test/exports/tests/commonjs.js b/test/exports/tests/commonjs.js new file mode 100644 index 000000000..fe77b792e --- /dev/null +++ b/test/exports/tests/commonjs.js @@ -0,0 +1,14 @@ +// checks that require works +const StreamrClient = require('streamr-client') + +console.info('const StreamrClient = require(\'streamr-client\'):', { StreamrClient }) + +const auth = StreamrClient.generateEthereumAccount() +const client = new StreamrClient({ + auth, +}) + +client.connect().then(() => { + console.info('success') + return client.disconnect() +}) diff --git a/test/exports/tests/esm.mjs b/test/exports/tests/esm.mjs new file mode 100644 index 000000000..45c39ab54 --- /dev/null +++ b/test/exports/tests/esm.mjs @@ -0,0 +1,16 @@ +// check esm works, as native and via webpack + babel. Also see typescript.ts +import DefaultExport, * as NamedExports from 'streamr-client' + +console.info('import DefaultExport, * as NamedExports from \'streamr-client\':', { DefaultExport, NamedExports }) + +const StreamrClient = DefaultExport + +const auth = StreamrClient.generateEthereumAccount() +const client = new StreamrClient({ + auth, +}) +console.assert(!!NamedExports.DataUnion, 'NamedExports should have DataUnion') +client.connect().then(() => { + console.info('success') + return client.disconnect() +}) diff --git a/test/exports/tests/typescript.ts b/test/exports/tests/typescript.ts new file mode 100644 index 000000000..7a68f57ee --- /dev/null +++ b/test/exports/tests/typescript.ts @@ -0,0 +1,19 @@ +// check ts esm works via tsc + +import DefaultExport, * as NamedExports from 'streamr-client' + +console.info('import DefaultExport, * as NamedExports from \'streamr-client\':', { DefaultExport, NamedExports }) + +const StreamrClient = DefaultExport + +const auth = StreamrClient.generateEthereumAccount() +const client = new StreamrClient({ + auth, +}) + +console.assert(!!NamedExports.DataUnion, 'NamedExports should have DataUnion') + +client.connect().then(() => { + console.info('success') + return client.disconnect() +}) diff --git a/test/exports/tsconfig.json b/test/exports/tsconfig.json new file mode 100644 index 000000000..7b216a526 --- /dev/null +++ b/test/exports/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@tsconfig/node14/tsconfig.json", + "compilerOptions": { + "allowJs": true, + "declaration": true, + "outDir": "dist", + "strict": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "module": "commonjs" + }, + "include": [ + "tests/typescript.ts" + ] +} diff --git a/test/exports/webpack.config.js b/test/exports/webpack.config.js new file mode 100644 index 000000000..3a89ee501 --- /dev/null +++ b/test/exports/webpack.config.js @@ -0,0 +1,47 @@ +/* eslint-disable prefer-template */ +/* eslint-disable prefer-destructuring */ + +process.env.NODE_ENV = process.env.NODE_ENV || 'development' // set a default NODE_ENV + +const path = require('path') + +module.exports = (env, argv) => { + const isProduction = argv.mode === 'production' || process.env.NODE_ENV === 'production' + + return { + mode: isProduction ? 'production' : 'development', + target: 'web', + entry: { + commonjs: path.join(__dirname, 'tests/commonjs.js'), + typescript: path.join(__dirname, 'tests/typescript.ts'), + esm: path.join(__dirname, 'tests/esm.mjs'), + }, + devtool: false, + output: { + filename: '[name].webpacked.js', + }, + optimization: { + minimize: false, + }, + module: { + rules: [ + { + test: /(\.jsx|\.js|\.ts)$/, + exclude: /(node_modules|bower_components)/, + use: { + loader: 'babel-loader', + options: { + configFile: path.resolve(__dirname, '../../.babel.config.js'), + babelrc: false, + cacheDirectory: true, + } + } + }, + ], + }, + resolve: { + modules: [path.resolve('./node_modules'), path.resolve('./tests/'), path.resolve('../../node_modules')], + extensions: ['.json', '.js', '.ts', '.mjs'], + }, + } +} diff --git a/test/flakey/EnvStressTest.test.js b/test/flakey/EnvStressTest.test.js index d1da5f4d9..60ceb263e 100644 --- a/test/flakey/EnvStressTest.test.js +++ b/test/flakey/EnvStressTest.test.js @@ -1,5 +1,5 @@ import { pTimeout } from '../../src/utils' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import { fakePrivateKey, uid } from '../utils' import config from '../integration/config' diff --git a/test/integration/Encryption.test.js b/test/integration/Encryption.test.js index 7f5d05a49..8b4475760 100644 --- a/test/integration/Encryption.test.js +++ b/test/integration/Encryption.test.js @@ -3,7 +3,7 @@ import { MessageLayer } from 'streamr-client-protocol' import { fakePrivateKey, uid, Msg, getPublishTestMessages } from '../utils' import { Defer } from '../../src/utils' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import { GroupKey } from '../../src/stream/Encryption' import Connection from '../../src/Connection' diff --git a/test/integration/GapFill.test.js b/test/integration/GapFill.test.js index 4271b30c8..7eb7dc8fa 100644 --- a/test/integration/GapFill.test.js +++ b/test/integration/GapFill.test.js @@ -1,7 +1,7 @@ import { wait } from 'streamr-test-utils' import { uid, fakePrivateKey, describeRepeats, getPublishTestMessages } from '../utils' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import Connection from '../../src/Connection' import config from './config' diff --git a/test/integration/LoginEndpoints.test.js b/test/integration/LoginEndpoints.test.js index a544a483b..5c1b35c93 100644 --- a/test/integration/LoginEndpoints.test.js +++ b/test/integration/LoginEndpoints.test.js @@ -2,7 +2,7 @@ import assert from 'assert' import { ethers } from 'ethers' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import config from './config' diff --git a/test/integration/MultipleClients.test.js b/test/integration/MultipleClients.test.js index 68d32f682..2869b92d5 100644 --- a/test/integration/MultipleClients.test.js +++ b/test/integration/MultipleClients.test.js @@ -2,7 +2,7 @@ import { wait } from 'streamr-test-utils' import { ControlLayer } from 'streamr-client-protocol' import { describeRepeats, uid, fakePrivateKey, getWaitForStorage, getPublishTestMessages, addAfterFn } from '../utils' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import { counterId, Defer, pLimitFn } from '../../src/utils' import Connection from '../../src/Connection' diff --git a/test/integration/ResendReconnect.test.js b/test/integration/ResendReconnect.test.js index dc650c20e..a777acc22 100644 --- a/test/integration/ResendReconnect.test.js +++ b/test/integration/ResendReconnect.test.js @@ -1,7 +1,7 @@ import { wait, waitForCondition } from 'streamr-test-utils' import { uid, fakePrivateKey, getPublishTestMessages } from '../utils' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import { Defer } from '../../src/utils' import config from './config' diff --git a/test/integration/Resends.test.js b/test/integration/Resends.test.js index 611dbcf25..836632117 100644 --- a/test/integration/Resends.test.js +++ b/test/integration/Resends.test.js @@ -1,7 +1,7 @@ import { wait, waitForCondition, waitForEvent } from 'streamr-test-utils' import { uid, describeRepeats, fakePrivateKey, getPublishTestMessages } from '../utils' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import { Defer, pTimeout } from '../../src/utils' import Connection from '../../src/Connection' diff --git a/test/integration/Sequencing.test.js b/test/integration/Sequencing.test.js index b75720d34..f9ee621bd 100644 --- a/test/integration/Sequencing.test.js +++ b/test/integration/Sequencing.test.js @@ -1,7 +1,7 @@ import { wait, waitForCondition, waitForEvent } from 'streamr-test-utils' import { uid, fakePrivateKey, getWaitForStorage } from '../utils' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import Connection from '../../src/Connection' import config from './config' diff --git a/test/integration/Session.test.js b/test/integration/Session.test.js index 90c9b0bf7..bccd20182 100644 --- a/test/integration/Session.test.js +++ b/test/integration/Session.test.js @@ -1,4 +1,4 @@ -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import { fakePrivateKey } from '../utils' import config from './config' diff --git a/test/integration/Stream.test.js b/test/integration/Stream.test.js index 56f112f1b..19df708a3 100644 --- a/test/integration/Stream.test.js +++ b/test/integration/Stream.test.js @@ -1,4 +1,4 @@ -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import { uid, fakePrivateKey, getPublishTestMessages } from '../utils' import config from './config' diff --git a/test/integration/StreamConnectionState.test.js b/test/integration/StreamConnectionState.test.js index 4c137da74..d8fcd8c4e 100644 --- a/test/integration/StreamConnectionState.test.js +++ b/test/integration/StreamConnectionState.test.js @@ -2,7 +2,7 @@ import { wait } from 'streamr-test-utils' import { ControlLayer } from 'streamr-client-protocol' import { uid, fakePrivateKey, describeRepeats, getPublishTestMessages } from '../utils' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import { Defer, pLimitFn } from '../../src/utils' import Connection from '../../src/Connection' diff --git a/test/integration/StreamEndpoints.test.ts b/test/integration/StreamEndpoints.test.ts index ccd617f6f..ea4e2672a 100644 --- a/test/integration/StreamEndpoints.test.ts +++ b/test/integration/StreamEndpoints.test.ts @@ -1,8 +1,8 @@ import { ethers, Wallet } from 'ethers' import { NotFoundError, ValidationError } from '../../src/rest/authFetch' -import Stream, { StreamOperation } from '../../src/stream' +import { Stream, StreamOperation } from '../../src/stream' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import { uid } from '../utils' import config from './config' diff --git a/test/integration/StreamrClient.test.js b/test/integration/StreamrClient.test.js index 77e3017ee..3f22997b8 100644 --- a/test/integration/StreamrClient.test.js +++ b/test/integration/StreamrClient.test.js @@ -6,7 +6,7 @@ import { ControlLayer, MessageLayer } from 'streamr-client-protocol' import { wait, waitForEvent } from 'streamr-test-utils' import { describeRepeats, uid, fakePrivateKey, getWaitForStorage, getPublishTestMessages, Msg } from '../utils' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import { Defer, pLimitFn } from '../../src/utils' import Connection from '../../src/Connection' diff --git a/test/integration/Subscriber.test.js b/test/integration/Subscriber.test.js index 379390e51..55800d3eb 100644 --- a/test/integration/Subscriber.test.js +++ b/test/integration/Subscriber.test.js @@ -2,7 +2,7 @@ import { ControlLayer } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' import { uid, fakePrivateKey, describeRepeats, getPublishTestMessages, collect } from '../utils' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import { Defer } from '../../src/utils' import Connection from '../../src/Connection' diff --git a/test/integration/SubscriberResends.test.js b/test/integration/SubscriberResends.test.js index 806ba8058..5193dcfe9 100644 --- a/test/integration/SubscriberResends.test.js +++ b/test/integration/SubscriberResends.test.js @@ -2,7 +2,7 @@ import { ControlLayer } from 'streamr-client-protocol' import { wait } from 'streamr-test-utils' import { Msg, uid, collect, describeRepeats, fakePrivateKey, getWaitForStorage, getPublishTestMessages } from '../utils' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import Connection from '../../src/Connection' import { Defer } from '../../src/utils' diff --git a/test/integration/Subscription.test.js b/test/integration/Subscription.test.js index b15dfadca..bdcd8dd17 100644 --- a/test/integration/Subscription.test.js +++ b/test/integration/Subscription.test.js @@ -1,7 +1,7 @@ import { wait, waitForEvent } from 'streamr-test-utils' import { uid, fakePrivateKey } from '../utils' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import config from './config' diff --git a/test/integration/Validation.test.js b/test/integration/Validation.test.js index 9951fb4c2..bcefc8b0f 100644 --- a/test/integration/Validation.test.js +++ b/test/integration/Validation.test.js @@ -1,7 +1,7 @@ import { wait } from 'streamr-test-utils' import { uid, fakePrivateKey, describeRepeats, getPublishTestMessages } from '../utils' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import Connection from '../../src/Connection' import config from './config' diff --git a/test/integration/authFetch.test.js b/test/integration/authFetch.test.js index bfef4d431..bb699a7c2 100644 --- a/test/integration/authFetch.test.js +++ b/test/integration/authFetch.test.js @@ -2,7 +2,7 @@ jest.mock('node-fetch') import fetch from 'node-fetch' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import { fakePrivateKey } from '../utils' import config from './config' diff --git a/test/integration/config.js b/test/integration/config.js index f103ad654..72a4d7f86 100644 --- a/test/integration/config.js +++ b/test/integration/config.js @@ -9,8 +9,12 @@ module.exports = { streamrNodeAddress: '0xFCAd0B19bB29D4674531d6f115237E16AfCE377c', tokenAddress: process.env.TOKEN_ADDRESS || '0xbAA81A0179015bE47Ad439566374F2Bae098686F', tokenAddressSidechain: process.env.TOKEN_ADDRESS_SIDECHAIN || '0x73Be21733CC5D08e1a14Ea9a399fb27DB3BEf8fF', - factoryMainnetAddress: process.env.DU_FACTORY_MAINNET || '0x5E959e5d5F3813bE5c6CeA996a286F734cc9593b', - factorySidechainAddress: process.env.DU_FACTORY_SIDECHAIN || '0x4081B7e107E59af8E82756F96C751174590989FE', + dataUnion: { + factoryMainnetAddress: process.env.DU_FACTORY_MAINNET || '0x4bbcBeFBEC587f6C4AF9AF9B48847caEa1Fe81dA', + factorySidechainAddress: process.env.DU_FACTORY_SIDECHAIN || '0x4A4c4759eb3b7ABee079f832850cD3D0dC48D927', + templateMainnetAddress: process.env.DU_TEMPLATE_MAINNET || '0x7bFBAe10AE5b5eF45e2aC396E0E605F6658eF3Bc', + templateSidechainAddress: process.env.DU_TEMPLATE_SIDECHAIN || '0x36afc8c9283CC866b8EB6a61C6e6862a83cd6ee8', + }, sidechain: { url: process.env.SIDECHAIN_URL || 'http://10.200.10.1:8546', timeout: process.env.TEST_TIMEOUT, diff --git a/test/integration/dataunion/adminFee.test.ts b/test/integration/dataunion/adminFee.test.ts index 18a2bd48e..0e25f2d1c 100644 --- a/test/integration/dataunion/adminFee.test.ts +++ b/test/integration/dataunion/adminFee.test.ts @@ -2,7 +2,7 @@ import { Contract, providers, Wallet } from 'ethers' import { parseEther, formatEther } from 'ethers/lib/utils' import debug from 'debug' -import StreamrClient from '../../../src/StreamrClient' +import { StreamrClient } from '../../../src/StreamrClient' import * as Token from '../../../contracts/TestToken.json' import config from '../config' diff --git a/test/integration/dataunion/calculate.test.ts b/test/integration/dataunion/calculate.test.ts index 57d7002d6..cba46a0e4 100644 --- a/test/integration/dataunion/calculate.test.ts +++ b/test/integration/dataunion/calculate.test.ts @@ -1,7 +1,7 @@ import { providers, Wallet } from 'ethers' import debug from 'debug' -import StreamrClient from '../../../src/StreamrClient' +import { StreamrClient } from '../../../src/StreamrClient' import config from '../config' import { createClient, expectInvalidAddress } from '../../utils' diff --git a/test/integration/dataunion/deploy.test.ts b/test/integration/dataunion/deploy.test.ts index 6caca2e77..c76d86aed 100644 --- a/test/integration/dataunion/deploy.test.ts +++ b/test/integration/dataunion/deploy.test.ts @@ -1,7 +1,7 @@ import { providers } from 'ethers' import debug from 'debug' -import StreamrClient from '../../../src/StreamrClient' +import { StreamrClient } from '../../../src/StreamrClient' import config from '../config' import { createMockAddress } from '../../utils' diff --git a/test/integration/dataunion/member.test.ts b/test/integration/dataunion/member.test.ts index 68adf9a51..53c445e4e 100644 --- a/test/integration/dataunion/member.test.ts +++ b/test/integration/dataunion/member.test.ts @@ -1,7 +1,7 @@ import { providers, Wallet } from 'ethers' import debug from 'debug' -import StreamrClient from '../../../src/StreamrClient' +import { StreamrClient } from '../../../src/StreamrClient' import config from '../config' import { DataUnion, JoinRequestState } from '../../../src/dataunion/DataUnion' import { createMockAddress, expectInvalidAddress, fakePrivateKey } from '../../utils' diff --git a/test/integration/dataunion/signature.test.ts b/test/integration/dataunion/signature.test.ts index d90a24fda..65281fc0b 100644 --- a/test/integration/dataunion/signature.test.ts +++ b/test/integration/dataunion/signature.test.ts @@ -3,7 +3,7 @@ import { parseEther } from 'ethers/lib/utils' import debug from 'debug' import { getEndpointUrl } from '../../../src/utils' -import StreamrClient from '../../../src/StreamrClient' +import { StreamrClient } from '../../../src/StreamrClient' import * as Token from '../../../contracts/TestToken.json' import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' import config from '../config' diff --git a/test/integration/dataunion/stats.test.ts b/test/integration/dataunion/stats.test.ts index 7ea3a36ae..d9bcfe911 100644 --- a/test/integration/dataunion/stats.test.ts +++ b/test/integration/dataunion/stats.test.ts @@ -1,7 +1,7 @@ import { providers } from 'ethers' import debug from 'debug' -import StreamrClient from '../../../src/StreamrClient' +import { StreamrClient } from '../../../src/StreamrClient' import config from '../config' import { DataUnion, MemberStatus } from '../../../src/dataunion/DataUnion' import { createClient, createMockAddress, expectInvalidAddress } from '../../utils' diff --git a/test/integration/dataunion/withdraw.test.ts b/test/integration/dataunion/withdraw.test.ts index cba025574..2b5cf0278 100644 --- a/test/integration/dataunion/withdraw.test.ts +++ b/test/integration/dataunion/withdraw.test.ts @@ -4,7 +4,7 @@ import { TransactionReceipt } from '@ethersproject/providers' import debug from 'debug' import { getEndpointUrl, until } from '../../../src/utils' -import StreamrClient from '../../../src/StreamrClient' +import { StreamrClient } from '../../../src/StreamrClient' import * as Token from '../../../contracts/TestToken.json' import * as DataUnionSidechain from '../../../contracts/DataUnionSidechain.json' import config from '../config' diff --git a/test/legacy/MessageCreationUtil.test.js b/test/legacy/MessageCreationUtil.test.js index 41189c460..66c77bb8e 100644 --- a/test/legacy/MessageCreationUtil.test.js +++ b/test/legacy/MessageCreationUtil.test.js @@ -4,7 +4,7 @@ import { wait } from 'streamr-test-utils' import { MessageLayer } from 'streamr-client-protocol' import { MessageCreationUtil, StreamPartitioner } from '../../src/Publisher' -import Stream from '../../src/stream' +import { Stream } from '../../src/stream' // eslint-disable-next-line import/no-named-as-default-member import StubbedStreamrClient from './StubbedStreamrClient' diff --git a/test/unit/Session.test.js b/test/unit/Session.test.js index 08046b45d..b273b79ec 100644 --- a/test/unit/Session.test.js +++ b/test/unit/Session.test.js @@ -1,6 +1,6 @@ import sinon from 'sinon' -import StreamrClient from '../../src/StreamrClient' +import { StreamrClient } from '../../src/StreamrClient' import { Defer } from '../../src/utils' import Session from '../../src/Session' import config from '../integration/config' diff --git a/test/unit/Stream.test.js b/test/unit/Stream.test.js index a0c1e0848..15f68dab6 100644 --- a/test/unit/Stream.test.js +++ b/test/unit/Stream.test.js @@ -1,4 +1,4 @@ -import Stream from '../../src/stream' +import { Stream } from '../../src/stream' describe('Stream', () => { let stream diff --git a/test/unit/StubbedStreamrClient.js b/test/unit/StubbedStreamrClient.js index 3eb8943e8..d1086d26f 100644 --- a/test/unit/StubbedStreamrClient.js +++ b/test/unit/StubbedStreamrClient.js @@ -1,5 +1,5 @@ -import StreamrClient from '../../src/' -import Stream from '../../src/stream' +import { StreamrClient } from '../../src/' +import { Stream } from '../../src/stream' export default class StubbedStreamrClient extends StreamrClient { getUserInfo() { diff --git a/test/utils.ts b/test/utils.ts index 3c068aba9..b691ff5ac 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -3,7 +3,7 @@ import { wait } from 'streamr-test-utils' import { providers, Wallet } from 'ethers' import { pTimeout, counterId, AggregatedError } from '../src/utils' import { validateOptions } from '../src/stream/utils' -import StreamrClient from '../src/StreamrClient' +import { StreamrClient } from '../src/StreamrClient' const crypto = require('crypto') const config = require('./integration/config') diff --git a/tsconfig.json b/tsconfig.json index 1daeea6fc..c9699f3c2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,30 +1,34 @@ { - "compilerOptions": { - "target": "ES2015", - "module": "commonjs", - "allowJs": true, - "declaration": true, - "declarationDir": "dist/types", - "outDir": "dist", - "lib": [ - "ES5", - "ES2015", - "ES2016", - "ES2017", - "ES2018", - "ES2019", - "ES2020", - "ESNext", - "DOM" + "compilerOptions": { + "target": "ES2015", + "module": "commonjs", + "allowJs": true, + "declaration": true, + "declarationDir": "dist/types", + "outDir": "dist", + "lib": [ + "ES5", + "ES2015", + "ES2016", + "ES2017", + "ES2018", + "ES2019", + "ES2020", + "ESNext", + "DOM" + ], + "strict": true, + "esModuleInterop": true, + "resolveJsonModule": true, + "moduleResolution": "node" + }, + "globals": { + "ts-jest": {} + }, + "include": [ + "src/**/*", + "vendor/**/*", + "contracts/**/*" ], - "strict": true, - "esModuleInterop": true, - "resolveJsonModule": true, - "moduleResolution": "node" - }, - "globals": { - "ts-jest": {} - }, - "include": ["src/**/*", "contracts/*"], - "exclude": ["node_modules", "dist"] + "exclude": ["node_modules", "dist"] } diff --git a/tsconfig.node.json b/tsconfig.node.json index 0b2d144c9..06a4b2762 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -4,7 +4,7 @@ "allowJs": true, "declaration": true, "declarationDir": "dist/types", - "outDir": "dist/node", + "outDir": "dist", "lib": [ "ES5", "ES2015", @@ -18,15 +18,15 @@ ], "strict": true, "moduleResolution": "node", - "resolveJsonModule": true + "resolveJsonModule": true, + "module": "commonjs" }, "globals": { "ts-jest": {} }, "include": [ "src/**/*", - "contracts/**/*", - // quick-lru is esm, need to transpile :/ - "node_modules/quick-lru/index.js" + "vendor/**/*", + "contracts/**/*" ] } diff --git a/webpack.config.js b/webpack.config.js index dd8dd3c43..c4952a461 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -54,7 +54,7 @@ module.exports = (env, argv) => { ], }, resolve: { - modules: [path.resolve('./node_modules'), path.resolve('./src')], + modules: [path.resolve('./node_modules'), path.resolve('./vendor'), path.resolve('./src')], extensions: ['.json', '.js', '.ts'], }, plugins: [ @@ -76,6 +76,19 @@ module.exports = (env, argv) => { libraryTarget: 'umd2', filename: libraryName + '.web.js', library: 'StreamrClient', + // NOTE: + // exporting the class directly + // `export default class StreamrClient {}` + // becomes: + // `window.StreamrClient === StreamrClient` + // which is correct, but if we define the class and export separately, + // which is required if we do interface StreamrClient extends …: + // `class StreamrClient {}; export default StreamrClient;` + // becomes: + // `window.StreamrClient = { default: StreamrClient, … }` + // which is wrong for browser builds. + // see: https://github.com/webpack/webpack/issues/706#issuecomment-438007763 + libraryExport: 'StreamrClient', // This fixes the above. }, resolve: { alias: { From ddef67832df0b98c221ce89794d4bc3abf83bc4a Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 11 Mar 2021 10:12:22 -0500 Subject: [PATCH 510/517] Fix line lengths, unify dataunion address validation messages. --- src/Config.ts | 8 +++- src/Connection.ts | 4 +- src/dataunion/Contracts.ts | 43 +++++++++----------- src/index.ts | 5 ++- test/benchmarks/publish.js | 3 -- test/benchmarks/subscribe.js | 1 - test/integration/dataunion/signature.test.ts | 2 +- test/integration/dataunion/stats.test.ts | 7 +++- 8 files changed, 39 insertions(+), 34 deletions(-) diff --git a/src/Config.ts b/src/Config.ts index 03bd9c89d..e67c17afc 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -48,7 +48,9 @@ export type StrictStreamrClientOptions = { } } -export type StreamrClientOptions = Partial & { dataUnion: Partial}> +export type StreamrClientOptions = Partial & { + dataUnion: Partial +}> const { ControlMessage } = ControlLayer const { StreamMessage } = MessageLayer @@ -87,7 +89,9 @@ export default function ClientConfig(opts: StreamrClientOptions = {}) { tokenAddress: '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', dataUnion: { minimumWithdrawTokenWei: '1000000', // Threshold value set in AMB configs, smallest token amount to pass over the bridge - freeWithdraw: false, // if someone else pays for the gas when transporting the withdraw tx to mainnet; otherwise the client does the transport as self-service and pays the mainnet gas costs + // if someone else pays for the gas when transporting the withdraw tx to mainnet; + // otherwise the client does the transport as self-service and pays the mainnet gas costs + freeWithdraw: false, factoryMainnetAddress: '0x7d55f9981d4E10A193314E001b96f72FCc901e40', factorySidechainAddress: '0x1b55587Beea0b5Bc96Bb2ADa56bD692870522e9f', templateMainnetAddress: '0x5FE790E3751dd775Cb92e9086Acd34a2adeB8C7b', diff --git a/src/Connection.ts b/src/Connection.ts index f593a4620..3b2a6977e 100644 --- a/src/Connection.ts +++ b/src/Connection.ts @@ -314,7 +314,9 @@ export default class Connection extends EventEmitter { constructor(options = {}, debug?: Debug.Debugger) { super() - this._debug = (debug !== undefined) ? debug.extend(counterId(this.constructor.name)) : Debug(`StreamrClient::${counterId(this.constructor.name)}`) + this._debug = debug !== undefined + ? debug.extend(counterId(this.constructor.name)) + : Debug(`StreamrClient::${counterId(this.constructor.name)}`) this.options = options this.options.autoConnect = !!this.options.autoConnect diff --git a/src/dataunion/Contracts.ts b/src/dataunion/Contracts.ts index f4f65f3c6..fa87273bf 100644 --- a/src/dataunion/Contracts.ts +++ b/src/dataunion/Contracts.ts @@ -14,6 +14,12 @@ import { StreamrClient } from '../StreamrClient' const log = debug('StreamrClient::DataUnion') +function validateAddress(name: string, address: EthereumAddress) { + if (!isAddress(address)) { + throw new Error(`${name} is ${address ? 'not a valid Ethereum address' : 'missing'}`) + } +} + export class Contracts { ethereum: StreamrEthereum @@ -41,13 +47,8 @@ export class Contracts { } getDataUnionMainnetAddress(dataUnionName: string, deployerAddress: EthereumAddress) { - if (!isAddress(this.factoryMainnetAddress)) { - throw new Error('StreamrClient factoryMainnetAddress configuration is ' + (this.factoryMainnetAddress ? 'not a valid Ethereum address' : 'missing')) - } - - if (!isAddress(this.templateMainnetAddress)) { - throw new Error('StreamrClient templateMainnetAddress configuration is ' + (this.templateMainnetAddress ? 'not a valid Ethereum address' : 'missing')) - } + validateAddress('StreamrClient factoryMainnetAddress', this.factoryMainnetAddress) + validateAddress('StreamrClient templateMainnetAddress', this.templateMainnetAddress) // This magic hex comes from https://github.com/streamr-dev/data-union-solidity/blob/master/contracts/CloneLib.sol#L19 const codeHash = keccak256(`0x3d602d80600a3d3981f3363d3d373d3d3d363d73${this.templateMainnetAddress.slice(2)}5af43d82803e903d91602b57fd5bf3`) const salt = keccak256(defaultAbiCoder.encode(['string', 'address'], [dataUnionName, deployerAddress])) @@ -61,24 +62,18 @@ export class Contracts { } getDataUnionSidechainAddress(mainnetAddress: EthereumAddress) { - if (!isAddress(this.factorySidechainAddress)) { - throw new Error('StreamrClient factorySidechainAddress configuration is ' + (this.factorySidechainAddress ? 'not a valid Ethereum address' : 'missing')) - } - - if (!isAddress(this.templateSidechainAddress)) { - throw new Error('StreamrClient templateSidechainAddress configuration is ' + (this.templateSidechainAddress ? 'not a valid Ethereum address' : 'missing')) - } + validateAddress('StreamrClient factorySidechainAddress', this.factorySidechainAddress) + validateAddress('StreamrClient templateSidechainAddress', this.templateSidechainAddress) // This magic hex comes from https://github.com/streamr-dev/data-union-solidity/blob/master/contracts/CloneLib.sol#L19 - const codeHash = keccak256(`0x3d602d80600a3d3981f3363d3d373d3d3d363d73${this.templateSidechainAddress.slice(2)}5af43d82803e903d91602b57fd5bf3`) + const code = `0x3d602d80600a3d3981f3363d3d373d3d3d363d73${this.templateSidechainAddress.slice(2)}5af43d82803e903d91602b57fd5bf3` + const codeHash = keccak256(code) return getCreate2Address(this.factorySidechainAddress, hexZeroPad(mainnetAddress, 32), codeHash) } getMainnetContractReadOnly(contractAddress: EthereumAddress) { - if (isAddress(contractAddress)) { - const provider = this.ethereum.getMainnetProvider() - return new Contract(contractAddress, dataUnionMainnetABI, provider) - } - throw new Error(`${contractAddress} was not a good Ethereum address`) + validateAddress('contractAddress', contractAddress) + const provider = this.ethereum.getMainnetProvider() + return new Contract(contractAddress, dataUnionMainnetABI, provider) } getMainnetContract(contractAddress: EthereumAddress) { @@ -298,12 +293,12 @@ export class Contracts { throw new Error(`Mainnet data union "${duName}" contract ${duMainnetAddress} already exists!`) } - if (!isAddress(this.factoryMainnetAddress)) { - throw new Error('StreamrClient has invalid factoryMainnetAddress configuration.') - } + validateAddress('StreamrClient factoryMainnetAddress', this.factoryMainnetAddress) if (await mainnetProvider.getCode(this.factoryMainnetAddress) === '0x') { - throw new Error(`Data union factory contract not found at ${this.factoryMainnetAddress}, check StreamrClient.options.dataUnion.factoryMainnetAddress!`) + throw new Error( + `Data union factory contract not found at ${this.factoryMainnetAddress}, check StreamrClient.options.dataUnion.factoryMainnetAddress!` + ) } const factoryMainnet = new Contract(this.factoryMainnetAddress!, factoryMainnetABI, mainnetWallet) diff --git a/src/index.ts b/src/index.ts index bb4b4c70d..1f6d66e9b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -13,7 +13,10 @@ export * from './dataunion/DataUnion' export * from './rest/authFetch' export * from './types' -// TODO should export these to support StreamMessageAsObject: export { StreamMessageType, ContentType, EncryptionType, SignatureType } from 'streamr-client-protocol/dist/src/protocol/message_layer/StreamMessage' +// TODO should export these to support StreamMessageAsObject: +// export { +// StreamMessageType, ContentType, EncryptionType, SignatureType +// } from 'streamr-client-protocol/dist/src/protocol/message_layer/StreamMessage' export { BigNumber } from '@ethersproject/bignumber' export { ConnectionInfo } from '@ethersproject/web' export { Contract } from '@ethersproject/contracts' diff --git a/test/benchmarks/publish.js b/test/benchmarks/publish.js index a98a158da..131cca6b4 100644 --- a/test/benchmarks/publish.js +++ b/test/benchmarks/publish.js @@ -1,13 +1,10 @@ const { format } = require('util') - const { Benchmark } = require('benchmark') // eslint-disable-next-line import/no-unresolved const StreamrClient = require('../../dist') const config = require('../integration/config') -console.log('StreamrClient', { StreamrClient }) - /* eslint-disable no-console */ let count = 100000 // pedantic: use large initial number so payload size is similar diff --git a/test/benchmarks/subscribe.js b/test/benchmarks/subscribe.js index 77c8a658a..afbb911ef 100644 --- a/test/benchmarks/subscribe.js +++ b/test/benchmarks/subscribe.js @@ -1,5 +1,4 @@ const { format } = require('util') - const { Benchmark } = require('benchmark') // eslint-disable-next-line import/no-unresolved diff --git a/test/integration/dataunion/signature.test.ts b/test/integration/dataunion/signature.test.ts index 65281fc0b..9f655e856 100644 --- a/test/integration/dataunion/signature.test.ts +++ b/test/integration/dataunion/signature.test.ts @@ -67,6 +67,7 @@ describe('DataUnion signature', () => { const isValid3 = await sidechainContract.signatureIsValid(memberWallet.address, member2Wallet.address, '3000000000000000', signature3) log(`Signature for all tokens ${memberWallet.address} -> ${member2Wallet.address}: ${signature}, checked ${isValid ? 'OK' : '!!!BROKEN!!!'}`) log(`Signature for 1 token ${memberWallet.address} -> ${member2Wallet.address}: ${signature2}, checked ${isValid2 ? 'OK' : '!!!BROKEN!!!'}`) + // eslint-disable-next-line max-len log(`Signature for 0.003 tokens ${memberWallet.address} -> ${member2Wallet.address}: ${signature3}, checked ${isValid3 ? 'OK' : '!!!BROKEN!!!'}`) log(`sidechainDU(${sidechainContract.address}) token bal ${await tokenSidechain.balanceOf(sidechainContract.address)}`) @@ -74,5 +75,4 @@ describe('DataUnion signature', () => { expect(isValid2).toBe(true) expect(isValid3).toBe(true) }, 100000) - }) diff --git a/test/integration/dataunion/stats.test.ts b/test/integration/dataunion/stats.test.ts index d9bcfe911..786181d35 100644 --- a/test/integration/dataunion/stats.test.ts +++ b/test/integration/dataunion/stats.test.ts @@ -56,7 +56,12 @@ describe('DataUnion stats', () => { }, 150000) it('member stats', async () => { - const memberStats = await Promise.all(activeMemberAddressList.concat([inactiveMember]).map((m) => queryClient.getDataUnion(dataUnion.getAddress()).getMemberStats(m))) + const memberStats = await Promise.all( + activeMemberAddressList + .concat([inactiveMember]) + .map((m) => queryClient.getDataUnion(dataUnion.getAddress()).getMemberStats(m)) + ) + const ZERO = BigNumber.from(0) expect(memberStats).toMatchObject([{ status: MemberStatus.ACTIVE, From ec31136ccb201509be232c0c4146d41d300a12cb Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 11 Mar 2021 10:22:41 -0500 Subject: [PATCH 511/517] Release v5.0.0-beta.8 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index d93fdbc59..e50711862 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.7", + "version": "5.0.0-beta.8", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index c3a37e03f..bf37f729d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.7", + "version": "5.0.0-beta.8", "description": "JavaScript client library for Streamr", "repository": { "type": "git", From 32224afb792c5d2615a49da1a32546267f745409 Mon Sep 17 00:00:00 2001 From: Tim Oxley Date: Thu, 11 Mar 2021 10:25:37 -0500 Subject: [PATCH 512/517] Release v5.0.0-beta.9 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index e50711862..a0fff2742 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.8", + "version": "5.0.0-beta.9", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index bf37f729d..013f7d9eb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.8", + "version": "5.0.0-beta.9", "description": "JavaScript client library for Streamr", "repository": { "type": "git", From 585edf18179452b9c1fbd81ed6e214c4eb486847 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Thu, 11 Mar 2021 21:33:11 +0200 Subject: [PATCH 513/517] privateKey type: EthereumAddress -> string|BytesLike (#215) --- src/Config.ts | 3 ++- src/Session.ts | 4 ++-- src/index.ts | 1 + test/unit/Config.test.ts | 23 +++++++++++++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 test/unit/Config.test.ts diff --git a/src/Config.ts b/src/Config.ts index e67c17afc..166ac69e3 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -5,12 +5,13 @@ import { BigNumber } from '@ethersproject/bignumber' import { getVersionString } from './utils' import { ConnectionInfo } from '@ethersproject/web' import { EthereumAddress, Todo } from './types' +import { BytesLike } from '@ethersproject/bytes' export type EthereumConfig = ExternalProvider|JsonRpcFetchFunc export type StrictStreamrClientOptions = { auth: { - privateKey?: EthereumAddress + privateKey?: string|BytesLike ethereum?: EthereumConfig apiKey?: string username?: string diff --git a/src/Session.ts b/src/Session.ts index ef349071b..a1292012c 100644 --- a/src/Session.ts +++ b/src/Session.ts @@ -2,7 +2,7 @@ import EventEmitter from 'eventemitter3' import { Wallet } from '@ethersproject/wallet' import { ExternalProvider, JsonRpcFetchFunc, Web3Provider } from '@ethersproject/providers' import { StreamrClient } from './StreamrClient' -import { EthereumAddress } from './types' +import { BytesLike } from '@ethersproject/bytes' enum State { LOGGING_OUT = 'logging out', @@ -12,7 +12,7 @@ enum State { } export interface SessionOptions { - privateKey?: EthereumAddress + privateKey?: string|BytesLike ethereum?: ExternalProvider|JsonRpcFetchFunc apiKey?: string username?: string diff --git a/src/index.ts b/src/index.ts index 1f6d66e9b..c113fec71 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,7 @@ export * from './types' export { BigNumber } from '@ethersproject/bignumber' export { ConnectionInfo } from '@ethersproject/web' export { Contract } from '@ethersproject/contracts' +export { BytesLike, Bytes } from '@ethersproject/bytes' export { TransactionReceipt, TransactionResponse } from '@ethersproject/providers' export default StreamrClient diff --git a/test/unit/Config.test.ts b/test/unit/Config.test.ts new file mode 100644 index 000000000..b127e14c5 --- /dev/null +++ b/test/unit/Config.test.ts @@ -0,0 +1,23 @@ +import { arrayify, BytesLike } from '@ethersproject/bytes' +import { StreamrClient } from '../../src/StreamrClient' + +const createClient = (privateKey: string|BytesLike) => { + return new StreamrClient({ + auth: { + privateKey + } + }) +} + +describe('Config', () => { + describe('private key', () => { + it('string', () => { + const client = createClient('0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF') + expect(client.getAddress()).toBe('0xFCAd0B19bB29D4674531d6f115237E16AfCE377c') + }) + it('byteslike', () => { + const client = createClient(arrayify('0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF')) + expect(client.getAddress()).toBe('0xFCAd0B19bB29D4674531d6f115237E16AfCE377c') + }) + }) +}) From 569146ec49a0eff630e084329f9521b12fede7de Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Thu, 11 Mar 2021 21:48:11 +0200 Subject: [PATCH 514/517] Simplify privateKey type --- src/Config.ts | 2 +- src/Session.ts | 2 +- test/unit/Config.test.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Config.ts b/src/Config.ts index 166ac69e3..fa1e68aa7 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -11,7 +11,7 @@ export type EthereumConfig = ExternalProvider|JsonRpcFetchFunc export type StrictStreamrClientOptions = { auth: { - privateKey?: string|BytesLike + privateKey?: BytesLike ethereum?: EthereumConfig apiKey?: string username?: string diff --git a/src/Session.ts b/src/Session.ts index a1292012c..0f4c8d7b7 100644 --- a/src/Session.ts +++ b/src/Session.ts @@ -12,7 +12,7 @@ enum State { } export interface SessionOptions { - privateKey?: string|BytesLike + privateKey?: BytesLike ethereum?: ExternalProvider|JsonRpcFetchFunc apiKey?: string username?: string diff --git a/test/unit/Config.test.ts b/test/unit/Config.test.ts index b127e14c5..d61d14c3f 100644 --- a/test/unit/Config.test.ts +++ b/test/unit/Config.test.ts @@ -1,7 +1,7 @@ import { arrayify, BytesLike } from '@ethersproject/bytes' import { StreamrClient } from '../../src/StreamrClient' -const createClient = (privateKey: string|BytesLike) => { +const createClient = (privateKey: BytesLike) => { return new StreamrClient({ auth: { privateKey From 4bdc63102d1760a63d95d27f4a9bfe7c1c6828a6 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Fri, 12 Mar 2021 08:41:36 +0200 Subject: [PATCH 515/517] Internal properties and methods (#214) Hide internal properties and methods from the public classes. Currently the internals are annotated with a weak /** @internal */ . Some of the properties/methods could be marked with TypeScript's private to force the encapsulation. That can be done later, after we have converted all classes to TypeScript and the usage boundaries are clear. --- src/Session.ts | 10 +- src/StreamrClient.ts | 35 ++++-- src/dataunion/DataUnion.ts | 11 +- src/rest/LoginEndpoints.ts | 7 ++ src/rest/StreamEndpoints.ts | 5 +- src/stream/StorageNode.ts | 4 +- src/stream/index.ts | 7 +- src/subscribe/{index.js => index.ts} | 140 ++++++++++++++++-------- test/integration/LoginEndpoints.test.js | 18 +-- test/unit/Session.test.js | 8 +- 10 files changed, 165 insertions(+), 80 deletions(-) rename src/subscribe/{index.js => index.ts} (85%) diff --git a/src/Session.ts b/src/Session.ts index 0f4c8d7b7..4e47f9e76 100644 --- a/src/Session.ts +++ b/src/Session.ts @@ -47,21 +47,21 @@ export default class Session extends EventEmitter { if (typeof this.options.privateKey !== 'undefined') { const wallet = new Wallet(this.options.privateKey) this.loginFunction = async () => ( - this._client.loginEndpoints.loginWithChallengeResponse((d: string) => wallet.signMessage(d), wallet.address) + this._client.loginWithChallengeResponse((d: string) => wallet.signMessage(d), wallet.address) ) } else if (typeof this.options.ethereum !== 'undefined') { const provider = new Web3Provider(this.options.ethereum) const signer = provider.getSigner() this.loginFunction = async () => ( - this._client.loginEndpoints.loginWithChallengeResponse((d: string) => signer.signMessage(d), await signer.getAddress()) + this._client.loginWithChallengeResponse((d: string) => signer.signMessage(d), await signer.getAddress()) ) } else if (typeof this.options.apiKey !== 'undefined') { this.loginFunction = async () => ( - this._client.loginEndpoints.loginWithApiKey(this.options.apiKey!) + this._client.loginWithApiKey(this.options.apiKey!) ) } else if (typeof this.options.username !== 'undefined' && typeof this.options.password !== 'undefined') { this.loginFunction = async () => ( - this._client.loginEndpoints.loginWithUsernamePassword(this.options.username!, this.options.password!) + this._client.loginWithUsernamePassword(this.options.username!, this.options.password!) ) } else { if (!this.options.sessionToken) { @@ -128,7 +128,7 @@ export default class Session extends EventEmitter { } this.updateState(State.LOGGING_OUT) - await this._client.loginEndpoints.logoutEndpoint() + await this._client.logoutEndpoint() this.options.sessionToken = undefined this.updateState(State.LOGGED_OUT) } diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index bcd105fcf..687614d94 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -9,7 +9,7 @@ import StreamrEthereum from './Ethereum' import Session from './Session' import Connection, { ConnectionError } from './Connection' import Publisher from './publish' -import { Subscriber } from './subscribe' +import { Subscriber, Subscription } from './subscribe' import { getUserId } from './user' import { Todo, MaybeAsync, EthereumAddress } from './types' import { StreamEndpoints } from './rest/StreamEndpoints' @@ -141,18 +141,24 @@ export interface StreamrClient extends StreamEndpoints, LoginEndpoints {} // eslint-disable-next-line no-redeclare export class StreamrClient extends EventEmitter { + /** @internal */ id: string + /** @internal */ debug: Debug.Debugger + /** @internal */ options: StrictStreamrClientOptions /** @internal */ session: Session + /** @internal */ connection: StreamrConnection + /** @internal */ publisher: Todo + /** @internal */ subscriber: Subscriber + /** @internal */ cached: StreamrCached + /** @internal */ ethereum: StreamrEthereum - streamEndpoints: StreamEndpoints - loginEndpoints: LoginEndpoints constructor(options: StreamrClientOptions = {}, connection?: StreamrConnection) { super() @@ -189,25 +195,29 @@ export class StreamrClient extends EventEmitter { this.subscriber = new Subscriber(this) this.ethereum = new StreamrEthereum(this) - this.streamEndpoints = Plugin(this, new StreamEndpoints(this)) - this.loginEndpoints = Plugin(this, new LoginEndpoints(this)) + Plugin(this, new StreamEndpoints(this)) + Plugin(this, new LoginEndpoints(this)) this.cached = new StreamrCached(this) } + /** @internal */ async onConnectionConnected() { this.debug('Connected!') this.emit('connected') } + /** @internal */ async onConnectionDisconnected() { this.debug('Disconnected.') this.emit('disconnected') } + /** @internal */ onConnectionError(err: Todo) { this.emit('error', new ConnectionError(err)) } + /** @internal */ getErrorEmitter(source: Todo) { return (err: Todo) => { if (!(err instanceof ConnectionError || err.reason instanceof ConnectionError)) { @@ -219,19 +229,20 @@ export class StreamrClient extends EventEmitter { } } + /** @internal */ _onError(err: Todo, ...args: Todo) { // @ts-expect-error this.onError(err, ...args) } + /** @internal */ async send(request: Todo) { return this.connection.send(request) } /** * Override to control output - */ - + * @internal */ onError(error: Todo) { // eslint-disable-line class-methods-use-this console.error(error) } @@ -256,6 +267,7 @@ export class StreamrClient extends EventEmitter { return this.connection.connect() } + /** @internal */ async nextConnection() { return this.connection.nextConnection() } @@ -297,10 +309,12 @@ export class StreamrClient extends EventEmitter { return getUserId(this) } + /** @internal */ setNextGroupKey(...args: Todo) { return this.publisher.setNextGroupKey(...args) } + /** @internal */ rotateGroupKey(...args: Todo) { return this.publisher.rotateGroupKey(...args) } @@ -340,7 +354,7 @@ export class StreamrClient extends EventEmitter { await this.subscriber.unsubscribe(opts) } - async resend(opts: Todo, onMessage?: OnMessageCallback) { + async resend(opts: Todo, onMessage?: OnMessageCallback): Promise { const task = this.subscriber.resend(opts) if (typeof onMessage !== 'function') { return task @@ -367,11 +381,11 @@ export class StreamrClient extends EventEmitter { return this.connection.enableAutoDisconnect(...args) } - getAddress() { + getAddress(): EthereumAddress { return this.ethereum.getAddress() } - async getPublisherId() { + async getPublisherId(): Promise { return this.getAddress() } @@ -406,6 +420,7 @@ export class StreamrClient extends EventEmitter { return DataUnion._deploy(options, this) // eslint-disable-line no-underscore-dangle } + /** @internal */ _getDataUnionFromName({ dataUnionName, deployerAddress }: { dataUnionName: string, deployerAddress: EthereumAddress}) { return DataUnion._fromName({ // eslint-disable-line no-underscore-dangle dataUnionName, diff --git a/src/dataunion/DataUnion.ts b/src/dataunion/DataUnion.ts index 2c92d2b77..6ba6ab0ed 100644 --- a/src/dataunion/DataUnion.ts +++ b/src/dataunion/DataUnion.ts @@ -68,10 +68,11 @@ const log = debug('StreamrClient::DataUnion') export class DataUnion { - contractAddress: EthereumAddress - sidechainAddress: EthereumAddress - client: StreamrClient + private contractAddress: EthereumAddress + private sidechainAddress: EthereumAddress + private client: StreamrClient + /** @internal */ constructor(contractAddress: EthereumAddress, sidechainAddress: EthereumAddress, client: StreamrClient) { // validate and convert to checksum case this.contractAddress = getAddress(contractAddress) @@ -460,6 +461,7 @@ export class DataUnion { * Create a new DataUnionMainnet contract to mainnet with DataUnionFactoryMainnet * This triggers DataUnionSidechain contract creation in sidechain, over the bridge (AMB) * @return that resolves when the new DU is deployed over the bridge to side-chain + * @internal */ static async _deploy(options: DataUnionDeployOptions = {}, client: StreamrClient): Promise { const deployerAddress = client.getAddress() @@ -513,18 +515,21 @@ export class DataUnion { // Internal functions + /** @internal */ static _fromContractAddress(contractAddress: string, client: StreamrClient) { const contracts = new Contracts(client) const sidechainAddress = contracts.getDataUnionSidechainAddress(getAddress(contractAddress)) // throws if bad address return new DataUnion(contractAddress, sidechainAddress, client) } + /** @internal */ static _fromName({ dataUnionName, deployerAddress }: { dataUnionName: string, deployerAddress: string}, client: StreamrClient) { const contracts = new Contracts(client) const contractAddress = contracts.getDataUnionMainnetAddress(dataUnionName, getAddress(deployerAddress)) // throws if bad address return DataUnion._fromContractAddress(contractAddress, client) // eslint-disable-line no-underscore-dangle } + /** @internal */ async _getContract() { const ret = this.getContracts().getMainnetContract(this.contractAddress) // @ts-expect-error diff --git a/src/rest/LoginEndpoints.ts b/src/rest/LoginEndpoints.ts index 56e38f89d..f67ec19e3 100644 --- a/src/rest/LoginEndpoints.ts +++ b/src/rest/LoginEndpoints.ts @@ -28,12 +28,14 @@ async function getSessionToken(url: string, props: any) { /** TODO the class should be annotated with at-internal, but adding the annotation hides the methods */ export class LoginEndpoints { + /** @internal */ client: StreamrClient constructor(client: StreamrClient) { this.client = client } + /** @internal */ async getChallenge(address: string) { this.client.debug('getChallenge %o', { address, @@ -48,6 +50,7 @@ export class LoginEndpoints { ) } + /** @internal */ async sendChallengeResponse(challenge: { challenge: string }, signature: string, address: string) { this.client.debug('sendChallengeResponse %o', { challenge, @@ -63,6 +66,7 @@ export class LoginEndpoints { return getSessionToken(url, props) } + /** @internal */ async loginWithChallengeResponse(signingFunction: (challenge: string) => Promise, address: string) { this.client.debug('loginWithChallengeResponse %o', { address, @@ -72,6 +76,7 @@ export class LoginEndpoints { return this.sendChallengeResponse(challenge, signature, address) } + /** @internal */ async loginWithApiKey(apiKey: string) { this.client.debug('loginWithApiKey %o', { apiKey, @@ -83,6 +88,7 @@ export class LoginEndpoints { return getSessionToken(url, props) } + /** @internal */ async loginWithUsernamePassword(username: string, password: string) { this.client.debug('loginWithUsernamePassword %o', { username, @@ -110,6 +116,7 @@ export class LoginEndpoints { return authFetch(`${this.client.options.restUrl}/users/me`, this.client.session) } + /** @internal */ async logoutEndpoint(): Promise { this.client.debug('logoutEndpoint') await authFetch(`${this.client.options.restUrl}/logout`, this.client.session, { diff --git a/src/rest/StreamEndpoints.ts b/src/rest/StreamEndpoints.ts index a1b9426f3..7005f47f5 100644 --- a/src/rest/StreamEndpoints.ts +++ b/src/rest/StreamEndpoints.ts @@ -79,6 +79,7 @@ function getKeepAliveAgentForUrl(url: string) { /** TODO the class should be annotated with at-internal, but adding the annotation hides the methods */ export class StreamEndpoints { + /** @internal */ client: StreamrClient constructor(client: StreamrClient) { @@ -171,7 +172,7 @@ export class StreamEndpoints { return json.addresses.map((a: string) => a.toLowerCase()) } - async isStreamPublisher(streamId: string, ethAddress: string) { + async isStreamPublisher(streamId: string, ethAddress: EthereumAddress) { this.client.debug('isStreamPublisher %o', { streamId, ethAddress, @@ -198,7 +199,7 @@ export class StreamEndpoints { return json.addresses.map((a: string) => a.toLowerCase()) } - async isStreamSubscriber(streamId: string, ethAddress: string) { + async isStreamSubscriber(streamId: string, ethAddress: EthereumAddress) { this.client.debug('isStreamSubscriber %o', { streamId, ethAddress, diff --git a/src/stream/StorageNode.ts b/src/stream/StorageNode.ts index 2e18d5767..ccb89b630 100644 --- a/src/stream/StorageNode.ts +++ b/src/stream/StorageNode.ts @@ -1,7 +1,9 @@ import { EthereumAddress } from '../types' export default class StorageNode { - _address: EthereumAddress + + private _address: EthereumAddress + constructor(address: EthereumAddress) { this._address = address } diff --git a/src/stream/index.ts b/src/stream/index.ts index 9a7496a6c..45c45465c 100644 --- a/src/stream/index.ts +++ b/src/stream/index.ts @@ -70,17 +70,21 @@ function getFieldType(value: any): (Field['type'] | undefined) { } export class Stream { - // TODO add field definitions for all fields // @ts-expect-error id: string // @ts-expect-error name: string + description?: string config: { fields: Field[]; } = { fields: [] } + partitions?: number + /** @internal */ _client: StreamrClient requireEncryptedData?: boolean requireSignedData?: boolean + storageDays?: number + inactivityThresholdHours?: number constructor(client: StreamrClient, props: StreamProperties) { this._client = client @@ -99,6 +103,7 @@ export class Stream { return json ? new Stream(this._client, json) : undefined } + /** @internal */ toObject() { const result = {} Object.keys(this).forEach((key) => { diff --git a/src/subscribe/index.js b/src/subscribe/index.ts similarity index 85% rename from src/subscribe/index.js rename to src/subscribe/index.ts index 591e51c32..b7ce2e205 100644 --- a/src/subscribe/index.js +++ b/src/subscribe/index.ts @@ -10,9 +10,33 @@ import MessagePipeline from './pipeline' import Validator from './Validator' import messageStream from './messageStream' import resendStream from './resendStream' +import { Todo } from '../types' +import StreamrClient from '..' export class Subscription extends Emitter { - constructor(client, opts, onFinally = () => {}) { + + streamId: string + streamPartition: number + /** @internal */ + client: StreamrClient + /** @internal */ + options: Todo + /** @internal */ + key: Todo + /** @internal */ + id: Todo + /** @internal */ + _onDone: Todo + /** @internal */ + _onFinally: Todo + /** @internal */ + pipeline: Todo + /** @internal */ + msgStream: Todo + /** @internal */ + iterated?: Todo + + constructor(client: StreamrClient, opts: Todo, onFinally = () => {}) { super() this.client = client this.options = validateOptions(opts) @@ -30,9 +54,10 @@ export class Subscription extends Emitter { this.pipeline = opts.pipeline || MessagePipeline(client, { ...this.options, validate, - onError: (err) => { + onError: (err: Todo) => { this.emit('error', err) }, + // @ts-expect-error }, this.onPipelineEnd) this.msgStream = this.pipeline.msgStream @@ -40,9 +65,9 @@ export class Subscription extends Emitter { /** * Expose cleanup + * @internal */ - - async onPipelineEnd(err) { + async onPipelineEnd(err: Todo) { try { await this._onFinally(err) } finally { @@ -50,6 +75,7 @@ export class Subscription extends Emitter { } } + /** @internal */ async onDone() { return this._onDone } @@ -58,8 +84,7 @@ export class Subscription extends Emitter { * Collect all messages into an array. * Returns array when subscription is ended. */ - - async collect(n) { + async collect(n?: Todo) { const msgs = [] for await (const msg of this) { if (n === 0) { @@ -75,6 +100,7 @@ export class Subscription extends Emitter { return msgs } + /** @internal */ [Symbol.asyncIterator]() { // only iterate sub once if (this.iterated) { @@ -85,19 +111,22 @@ export class Subscription extends Emitter { return this.pipeline } - async cancel(...args) { + /** @internal */ + async cancel(...args: Todo[]) { return this.pipeline.cancel(...args) } - async return(...args) { + /** @internal */ + async return(...args: Todo[]) { return this.pipeline.return(...args) } - async throw(...args) { + /** @internal */ + async throw(...args: Todo[]) { return this.pipeline.throw(...args) } - async unsubscribe(...args) { + async unsubscribe(...args: Todo[]) { return this.cancel(...args) } } @@ -107,9 +136,9 @@ export class Subscription extends Emitter { * Aggregates errors rather than throwing on first. */ -function multiEmit(emitters, ...args) { - let error - emitters.forEach((s) => { +function multiEmit(emitters: Todo, ...args: Todo[]) { + let error: Todo + emitters.forEach((s: Todo) => { try { s.emit(...args) } catch (err) { @@ -128,7 +157,15 @@ function multiEmit(emitters, ...args) { */ class SubscriptionSession extends Emitter { - constructor(client, options) { + + client: StreamrClient + options: Todo + validate: Todo + subscriptions: Set + deletedSubscriptions: Set + step?: Todo + + constructor(client: StreamrClient, options: Todo) { super() this.client = client this.options = validateOptions(options) @@ -229,6 +266,7 @@ class SubscriptionSession extends Emitter { await unsubscribe(this.client, this.options) } } + // @ts-expect-error ], check, { onError(err) { if (err instanceof ConnectionError && !check()) { @@ -241,7 +279,7 @@ class SubscriptionSession extends Emitter { }) } - has(sub) { + has(sub: Todo) { return this.subscriptions.has(sub) } @@ -250,14 +288,14 @@ class SubscriptionSession extends Emitter { * then on self. */ - emit(...args) { + emit(...args: Todo[]) { const subs = this._getSubs() try { multiEmit(subs, ...args) } catch (error) { return super.emit('error', error) } - + // @ts-expect-error return super.emit(...args) } @@ -273,7 +311,7 @@ class SubscriptionSession extends Emitter { * Add subscription & appropriate connection handle. */ - async add(sub) { + async add(sub: Todo) { this.subscriptions.add(sub) const { connection } = this.client await connection.addHandle(`adding${sub.id}`) @@ -289,7 +327,7 @@ class SubscriptionSession extends Emitter { * Remove subscription & appropriate connection handle. */ - async remove(sub) { + async remove(sub: Todo) { this.subscriptions.delete(sub) if (this.deletedSubscriptions.has(sub)) { @@ -328,12 +366,16 @@ class SubscriptionSession extends Emitter { */ class Subscriptions { - constructor(client) { + + client: StreamrClient + subSessions: Map + + constructor(client: StreamrClient) { this.client = client this.subSessions = new Map() } - async add(opts, onFinally = async () => {}) { + async add(opts: Todo, onFinally: Todo = async () => {}) { const options = validateOptions(opts) const { key } = options @@ -345,7 +387,8 @@ class Subscriptions { const sub = new Subscription(this.client, { ...options, validate: subSession.validate, - }, async (err) => { + // @ts-expect-error + }, async (err: Todo) => { try { await this.remove(sub) } finally { @@ -353,6 +396,7 @@ class Subscriptions { } }) + // @ts-expect-error sub.count = () => { // sub.count() gives number of subs on same stream+partition return this.count(sub.options) @@ -375,7 +419,7 @@ class Subscriptions { return sub } - async remove(sub) { + async remove(sub: Todo) { const { key } = sub let cancelTask try { @@ -397,10 +441,9 @@ class Subscriptions { /** * Remove all subscriptions, optionally only those matching options. */ - - async removeAll(options) { + async removeAll(options?: Todo) { const subs = this.get(options) - return allSettledValues(subs.map((sub) => ( + return allSettledValues(subs.map((sub: Todo) => ( this.remove(sub) ))) } @@ -421,7 +464,7 @@ class Subscriptions { * Count all matching subscriptions. */ - count(options) { + count(options: Todo) { if (options === undefined) { return this.countAll() } return this.get(options).length } @@ -441,7 +484,7 @@ class Subscriptions { * Get subscription session for matching sub options. */ - getSubscriptionSession(options) { + getSubscriptionSession(options: Todo) { const { key } = validateOptions(options) return this.subSessions.get(key) } @@ -450,7 +493,7 @@ class Subscriptions { * Get all subscriptions matching options. */ - get(options) { + get(options: Todo) { if (options === undefined) { return this.getAll() } const { key } = validateOptions(options) @@ -465,28 +508,35 @@ class Subscriptions { * Top-level user-facing interface for creating/destroying subscriptions. */ export class Subscriber { - constructor(client) { + + client: StreamrClient + subscriptions: Subscriptions + + constructor(client: StreamrClient) { this.client = client this.subscriptions = new Subscriptions(client) } - getSubscriptionSession(...args) { + getSubscriptionSession(...args: Todo[]) { + // @ts-expect-error return this.subscriptions.getSubscriptionSession(...args) } - getAll(...args) { + getAll(...args: Todo[]) { + // @ts-expect-error return this.subscriptions.getAll(...args) } - count(options) { + count(options: Todo[]) { return this.subscriptions.count(options) } - async subscribe(...args) { + async subscribe(...args: Todo[]) { + // @ts-expect-error return this.subscriptions.add(...args) } - async unsubscribe(options) { + async unsubscribe(options: Todo): Promise { if (options instanceof Subscription) { const sub = options return sub.cancel() @@ -499,7 +549,7 @@ export class Subscriber { return this.subscriptions.removeAll(options) } - async resend(opts) { + async resend(opts: Todo) { const resendMsgStream = resendStream(this.client, opts) const sub = new Subscription(this.client, { @@ -514,7 +564,7 @@ export class Subscriber { return sub } - async resendSubscribe(opts, onMessage) { + async resendSubscribe(opts: Todo, onMessage: Todo) { // This works by passing a custom message stream to a subscription // the custom message stream iterates resends, then iterates realtime const options = validateOptions(opts) @@ -523,7 +573,7 @@ export class Subscriber { const realtimeMessageStream = messageStream(this.client.connection, options) // cancel both streams on end - async function end(optionalErr) { + async function end(optionalErr: Todo) { await Promise.all([ resendMessageStream.cancel(optionalErr), realtimeMessageStream.cancel(optionalErr), @@ -534,15 +584,15 @@ export class Subscriber { } } - let resendSubscribeSub + let resendSubscribeSub: Todo - let lastResentMsgId - let lastProcessedMsgId + let lastResentMsgId: Todo + let lastProcessedMsgId: Todo const resendDone = Defer() let isResendDone = false let resentEmitted = false - function messageIDString(msg) { + function messageIDString(msg: Todo) { return msg.getMessageID().serialize() } @@ -579,14 +629,16 @@ export class Subscriber { } finally { isResendDone = true maybeEmitResend() + // @ts-expect-error resendDone.resolve() } }, - async function* ResendThenRealtime(src) { + async function* ResendThenRealtime(src: Todo) { yield* src await resendDone // ensure realtime doesn't start until resend ends yield* resendSubscribeSub.realtime }, + // @ts-expect-error ], end) const resendTask = resendMessageStream.subscribe() @@ -594,7 +646,7 @@ export class Subscriber { ...options, msgStream: it, afterSteps: [ - async function* detectEndOfResend(src) { + async function* detectEndOfResend(src: Todo) { for await (const msg of src) { const id = messageIDString(msg) try { diff --git a/test/integration/LoginEndpoints.test.js b/test/integration/LoginEndpoints.test.js index 5c1b35c93..ce4f94610 100644 --- a/test/integration/LoginEndpoints.test.js +++ b/test/integration/LoginEndpoints.test.js @@ -27,7 +27,7 @@ describe('LoginEndpoints', () => { describe('Challenge generation', () => { it('should retrieve a challenge', async () => { - const challenge = await client.loginEndpoints.getChallenge('some-address') + const challenge = await client.getChallenge('some-address') assert(challenge) assert(challenge.id) assert(challenge.challenge) @@ -38,7 +38,7 @@ describe('LoginEndpoints', () => { describe('Challenge response', () => { it('should fail to get a session token', async () => { await expect(async () => { - await client.loginEndpoints.sendChallengeResponse({ + await client.sendChallengeResponse({ id: 'some-id', challenge: 'some-challenge', }, 'some-sig', 'some-address') @@ -47,10 +47,10 @@ describe('LoginEndpoints', () => { it('should get a session token', async () => { const wallet = ethers.Wallet.createRandom() - const challenge = await client.loginEndpoints.getChallenge(wallet.address) + const challenge = await client.getChallenge(wallet.address) assert(challenge.challenge) const signature = await wallet.signMessage(challenge.challenge) - const sessionToken = await client.loginEndpoints.sendChallengeResponse(challenge, signature, wallet.address) + const sessionToken = await client.sendChallengeResponse(challenge, signature, wallet.address) assert(sessionToken) assert(sessionToken.token) assert(sessionToken.expires) @@ -58,7 +58,7 @@ describe('LoginEndpoints', () => { it('should get a session token with combined function', async () => { const wallet = ethers.Wallet.createRandom() - const sessionToken = await client.loginEndpoints.loginWithChallengeResponse((d) => wallet.signMessage(d), wallet.address) + const sessionToken = await client.loginWithChallengeResponse((d) => wallet.signMessage(d), wallet.address) assert(sessionToken) assert(sessionToken.token) assert(sessionToken.expires) @@ -73,7 +73,7 @@ describe('LoginEndpoints', () => { }) it('should get a session token', async () => { - const sessionToken = await client.loginEndpoints.loginWithApiKey('tester1-api-key') + const sessionToken = await client.loginWithApiKey('tester1-api-key') assert(sessionToken) assert(sessionToken.token) assert(sessionToken.expires) @@ -83,14 +83,14 @@ describe('LoginEndpoints', () => { describe('Username/password login', () => { it('should fail', async () => { await expect(async () => { - await client.loginEndpoints.loginWithUsernamePassword('username', 'password') + await client.loginWithUsernamePassword('username', 'password') }).rejects.toThrow('no longer supported') }) }) describe('UserInfo', () => { it('should get user info', async () => { - const userInfo = await client.loginEndpoints.getUserInfo() + const userInfo = await client.getUserInfo() assert(userInfo.name) assert(userInfo.username) }) @@ -100,7 +100,7 @@ describe('LoginEndpoints', () => { it('should not be able to use the same session token after logout', async () => { await client.getUserInfo() // first fetches the session token, then requests the endpoint const sessionToken1 = client.session.options.sessionToken - await client.loginEndpoints.logoutEndpoint() // invalidates the session token in engine-and-editor + await client.logoutEndpoint() // invalidates the session token in engine-and-editor await client.getUserInfo() // requests the endpoint with sessionToken1, receives 401, fetches a new session token const sessionToken2 = client.session.options.sessionToken assert.notDeepStrictEqual(sessionToken1, sessionToken2) diff --git a/test/unit/Session.test.js b/test/unit/Session.test.js index b273b79ec..a31a7d40a 100644 --- a/test/unit/Session.test.js +++ b/test/unit/Session.test.js @@ -23,9 +23,7 @@ describe('Session', () => { sessionToken: 'session-token', }, }) - clientSessionToken.loginEndpoints = { - logoutEndpoint: sinon.stub().resolves() - } + clientSessionToken.logoutEndpoint = sinon.stub().resolves() session = new Session(clientSessionToken) session.options.unauthenticated = false @@ -162,7 +160,7 @@ describe('Session', () => { it('should call the logout endpoint', async () => { await session.getSessionToken() await session.logout() - expect(clientSessionToken.loginEndpoints.logoutEndpoint.calledOnce).toBeTruthy() + expect(clientSessionToken.logoutEndpoint.calledOnce).toBeTruthy() }) it('should call the logout endpoint again', async () => { @@ -171,7 +169,7 @@ describe('Session', () => { await session.logout() await session.getSessionToken() await session.logout() - expect(clientSessionToken.loginEndpoints.logoutEndpoint.calledTwice).toBeTruthy() + expect(clientSessionToken.logoutEndpoint.calledTwice).toBeTruthy() }) it('should throw if already logging out', async () => { From 217887d546716da9531a0c2c2b1af96ee611d5b3 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Fri, 12 Mar 2021 18:03:20 +0200 Subject: [PATCH 516/517] Upgrade MetaMask support (#217) Fetch the account address from MetaMask using request({ method: 'eth_requestAccounts' }). Previously used the deprecated selectedAddress field. Configure chainId to StreamrClient options. The parameter is required when using MetaMask sidechain signing. BREAKING CHANGE: client.getAddress() is now async We could be more explicit about network chainIds. Sidechain chainId could be a required parameter in StreamrClient options and we could also add chainId parameter to options.mainnet definition. The chainId validation can be enhanced in another PR later, if needed. Note that there are currently no automated tests to check the MetaMask integration. --- examples/node/package-lock.json | 2 +- examples/webpack/package-lock.json | 2 +- package-lock.json | 2 +- package.json | 2 +- src/Config.ts | 9 ++++++--- src/Ethereum.js | 18 +++++++++++------- src/StreamrClient.ts | 2 +- src/dataunion/DataUnion.ts | 6 +++--- test/integration/dataunion/adminFee.test.ts | 2 +- test/integration/dataunion/deploy.test.ts | 2 +- test/unit/Config.test.ts | 8 ++++---- 11 files changed, 31 insertions(+), 24 deletions(-) diff --git a/examples/node/package-lock.json b/examples/node/package-lock.json index 2da623db7..03917af90 100644 --- a/examples/node/package-lock.json +++ b/examples/node/package-lock.json @@ -742,7 +742,7 @@ "qs": "^6.9.4", "randomstring": "^1.1.5", "receptacle": "^1.3.2", - "streamr-client-protocol": "^5.0.0-beta.9", + "streamr-client-protocol": "^5.0.0-beta.10", "uuid": "^8.2.0", "webpack-node-externals": "^1.7.2", "ws": "^7.3.0" diff --git a/examples/webpack/package-lock.json b/examples/webpack/package-lock.json index 4bf6faf70..88cefd538 100644 --- a/examples/webpack/package-lock.json +++ b/examples/webpack/package-lock.json @@ -5035,7 +5035,7 @@ "qs": "^6.9.4", "randomstring": "^1.1.5", "receptacle": "^1.3.2", - "streamr-client-protocol": "^5.0.0-beta.9", + "streamr-client-protocol": "^5.0.0-beta.10", "uuid": "^8.2.0", "webpack-node-externals": "^1.7.2", "ws": "^7.3.0" diff --git a/package-lock.json b/package-lock.json index a0fff2742..420847bd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.9", + "version": "5.0.0-beta.10", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 013f7d9eb..9834a2dd7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "streamr-client", - "version": "5.0.0-beta.9", + "version": "5.0.0-beta.10", "description": "JavaScript client library for Streamr", "repository": { "type": "git", diff --git a/src/Config.ts b/src/Config.ts index fa1e68aa7..156c6bd94 100644 --- a/src/Config.ts +++ b/src/Config.ts @@ -33,7 +33,7 @@ export type StrictStreamrClientOptions = { groupKeys: Todo keyExchange: Todo mainnet?: ConnectionInfo|string - sidechain?: ConnectionInfo|string + sidechain: ConnectionInfo & { chainId?: number } tokenAddress: EthereumAddress, dataUnion: { minimumWithdrawTokenWei: BigNumber|number|string @@ -86,7 +86,10 @@ export default function ClientConfig(opts: StreamrClientOptions = {}) { // Ethereum and Data Union related options // For ethers.js provider params, see https://docs.ethers.io/ethers.js/v5-beta/api-providers.html#provider mainnet: undefined, // Default to ethers.js default provider settings - sidechain: 'https://rpc.xdaichain.com/', + sidechain: { + url: 'https://rpc.xdaichain.com/', + chainId: 100 + }, tokenAddress: '0x0Cf0Ee63788A0849fE5297F3407f701E122cC023', dataUnion: { minimumWithdrawTokenWei: '1000000', // Threshold value set in AMB configs, smallest token amount to pass over the bridge @@ -106,7 +109,7 @@ export default function ClientConfig(opts: StreamrClientOptions = {}) { const options: StrictStreamrClientOptions = { ...defaults, - ...opts, + ...opts, // sidechain is not merged with the defaults dataUnion: { ...defaults.dataUnion, ...opts.dataUnion diff --git a/src/Ethereum.js b/src/Ethereum.js index dd39f568d..f159ec236 100644 --- a/src/Ethereum.js +++ b/src/Ethereum.js @@ -19,15 +19,18 @@ export default class StreamrEthereum { if (auth.privateKey) { const key = auth.privateKey const address = getAddress(computeAddress(key)) - this._getAddress = () => address + this._getAddress = async () => address this.getSigner = () => new Wallet(key, this.getMainnetProvider()) this.getSidechainSigner = async () => new Wallet(key, this.getSidechainProvider()) } else if (auth.ethereum) { - this._getAddress = () => { - if (auth.ethereum.selectedAddress) { + this._getAddress = async () => { + try { + const accounts = await auth.ethereum.request({ method: 'eth_requestAccounts' }) + const account = getAddress(accounts[0]) // convert to checksum case + return account + } catch { throw new Error('no addresses connected+selected in Metamask') } - return getAddress(auth.ethereum.selectedAddress) } this._getSigner = () => { const metamaskProvider = new Web3Provider(auth.ethereum) @@ -35,7 +38,6 @@ export default class StreamrEthereum { return metamaskSigner } this._getSidechainSigner = async () => { - // chainId is required for checking when using Metamask if (!options.sidechain || !options.sidechain.chainId) { throw new Error('Streamr sidechain not configured (with chainId) in the StreamrClient options!') } @@ -43,7 +45,9 @@ export default class StreamrEthereum { const metamaskProvider = new Web3Provider(auth.ethereum) const { chainId } = await metamaskProvider.getNetwork() if (chainId !== options.sidechain.chainId) { - throw new Error(`Please connect Metamask to Ethereum blockchain with chainId ${options.sidechain.chainId}`) + throw new Error( + `Please connect Metamask to Ethereum blockchain with chainId ${options.sidechain.chainId}: current chainId is ${chainId}` + ) } const metamaskSigner = metamaskProvider.getSigner() return metamaskSigner @@ -57,7 +61,7 @@ export default class StreamrEthereum { } } - getAddress() { + async getAddress() { if (!this._getAddress) { // _getAddress is assigned in constructor throw new Error('StreamrClient is not authenticated with private key') diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index 687614d94..5c7350b79 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -381,7 +381,7 @@ export class StreamrClient extends EventEmitter { return this.connection.enableAutoDisconnect(...args) } - getAddress(): EthereumAddress { + async getAddress(): Promise { return this.ethereum.getAddress() } diff --git a/src/dataunion/DataUnion.ts b/src/dataunion/DataUnion.ts index 6ba6ab0ed..bbf32e2d1 100644 --- a/src/dataunion/DataUnion.ts +++ b/src/dataunion/DataUnion.ts @@ -94,7 +94,7 @@ export class DataUnion { * Send a joinRequest, or get into data union instantly with a data union secret */ async join(secret?: string): Promise { - const memberAddress = this.client.getAddress() as string + const memberAddress = await this.client.getAddress() const body: any = { memberAddress } @@ -132,7 +132,7 @@ export class DataUnion { * @returns receipt once withdraw is complete (tokens are seen in mainnet) */ async withdrawAll(options?: DataUnionWithdrawOptions): Promise { - const recipientAddress = this.client.getAddress() + const recipientAddress = await this.client.getAddress() return this._executeWithdraw( () => this.getWithdrawAllTx(), recipientAddress, @@ -464,7 +464,7 @@ export class DataUnion { * @internal */ static async _deploy(options: DataUnionDeployOptions = {}, client: StreamrClient): Promise { - const deployerAddress = client.getAddress() + const deployerAddress = await client.getAddress() const { owner, joinPartAgents, diff --git a/test/integration/dataunion/adminFee.test.ts b/test/integration/dataunion/adminFee.test.ts index 0e25f2d1c..d4c273a0e 100644 --- a/test/integration/dataunion/adminFee.test.ts +++ b/test/integration/dataunion/adminFee.test.ts @@ -41,7 +41,7 @@ describe('DataUnion admin fee', () => { const dataUnion = await adminClient.deployDataUnion() const oldFee = await dataUnion.getAdminFee() log(`DU owner: ${await dataUnion.getAdminAddress()}`) - log(`Sending tx from ${adminClient.getAddress()}`) + log(`Sending tx from ${await adminClient.getAddress()}`) const tr = await dataUnion.setAdminFee(0.1) log(`Transaction receipt: ${JSON.stringify(tr)}`) const newFee = await dataUnion.getAdminFee() diff --git a/test/integration/dataunion/deploy.test.ts b/test/integration/dataunion/deploy.test.ts index c76d86aed..123f273fa 100644 --- a/test/integration/dataunion/deploy.test.ts +++ b/test/integration/dataunion/deploy.test.ts @@ -34,7 +34,7 @@ describe('DataUnion deploy', () => { it('not specified: defaults to deployer', async () => { const dataUnion = await adminClient.deployDataUnion() - expect(await dataUnion.getAdminAddress()).toBe(adminClient.getAddress()) + expect(await dataUnion.getAdminAddress()).toBe(await adminClient.getAddress()) }, 60000) it('specified', async () => { diff --git a/test/unit/Config.test.ts b/test/unit/Config.test.ts index d61d14c3f..1a43e613f 100644 --- a/test/unit/Config.test.ts +++ b/test/unit/Config.test.ts @@ -11,13 +11,13 @@ const createClient = (privateKey: BytesLike) => { describe('Config', () => { describe('private key', () => { - it('string', () => { + it('string', async () => { const client = createClient('0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF') - expect(client.getAddress()).toBe('0xFCAd0B19bB29D4674531d6f115237E16AfCE377c') + expect(await client.getAddress()).toBe('0xFCAd0B19bB29D4674531d6f115237E16AfCE377c') }) - it('byteslike', () => { + it('byteslike', async () => { const client = createClient(arrayify('0x0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF')) - expect(client.getAddress()).toBe('0xFCAd0B19bB29D4674531d6f115237E16AfCE377c') + expect(await client.getAddress()).toBe('0xFCAd0B19bB29D4674531d6f115237E16AfCE377c') }) }) }) From fb3eed1c4873cf489b6abb77d8893d104a249f28 Mon Sep 17 00:00:00 2001 From: Teo Gebhard Date: Fri, 12 Mar 2021 18:28:26 +0200 Subject: [PATCH 517/517] Public type definitions (#218) Define valid types for public API methods Also small bug fix for parsing stream definition options (e.g. when filtering subscriptions with getSubscription(opts)) --- src/Connection.ts | 18 ++++++++--- src/StreamrClient.ts | 44 +++++++++++++++++---------- src/rest/StreamEndpoints.ts | 6 ++-- src/stream/index.ts | 10 +++++-- src/stream/{utils.js => utils.ts} | 33 +++++++++++--------- src/subscribe/index.ts | 38 ++++++++++++----------- test/unit/StreamUtils.test.ts | 50 +++++++++++++++++++++++++++++++ test/utils.ts | 11 +++++++ 8 files changed, 154 insertions(+), 56 deletions(-) rename src/stream/{utils.js => utils.ts} (84%) create mode 100644 test/unit/StreamUtils.test.ts diff --git a/src/Connection.ts b/src/Connection.ts index 3b2a6977e..92c3fdb52 100644 --- a/src/Connection.ts +++ b/src/Connection.ts @@ -13,6 +13,16 @@ const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms)) // @ts-expect-error Debug.formatters.n = (v) => Debug.humanize(v) +export interface ConnectionOptions { + url?: string, + autoConnect?: boolean + autoDisconnect?: boolean + disconnectDelay?: number + maxRetries?: number + retryBackoffFactor?: number + maxRetryWait?: number +} + export class ConnectionError extends Error { reason?: Todo @@ -120,7 +130,7 @@ const STATE = { } /* eslint-disable no-underscore-dangle, no-param-reassign */ -function SocketConnector(connection: Todo) { +function SocketConnector(connection: Connection) { let next: Todo let socket: Todo let startedConnecting = false @@ -189,7 +199,7 @@ function SocketConnector(connection: Todo) { // connect async () => { startedConnecting = true - socket = await OpenWebSocket(connection.options.url, { + socket = await OpenWebSocket(connection.options.url!, { perMessageDeflate: false, debug: connection._debug, }) @@ -287,7 +297,7 @@ const DEFAULT_MAX_RETRIES = 10 export default class Connection extends EventEmitter { _debug: Todo - options: Todo + options: ConnectionOptions retryCount: Todo wantsState: Todo connectionHandles: Todo @@ -312,7 +322,7 @@ export default class Connection extends EventEmitter { })) } - constructor(options = {}, debug?: Debug.Debugger) { + constructor(options: ConnectionOptions = {}, debug?: Debug.Debugger) { super() this._debug = debug !== undefined ? debug.extend(counterId(this.constructor.name)) diff --git a/src/StreamrClient.ts b/src/StreamrClient.ts index 5c7350b79..b688e0bb5 100644 --- a/src/StreamrClient.ts +++ b/src/StreamrClient.ts @@ -7,7 +7,7 @@ import { validateOptions } from './stream/utils' import Config, { StreamrClientOptions, StrictStreamrClientOptions } from './Config' import StreamrEthereum from './Ethereum' import Session from './Session' -import Connection, { ConnectionError } from './Connection' +import Connection, { ConnectionError, ConnectionOptions } from './Connection' import Publisher from './publish' import { Subscriber, Subscription } from './subscribe' import { getUserId } from './user' @@ -18,10 +18,21 @@ import { DataUnion, DataUnionDeployOptions } from './dataunion/DataUnion' import { BigNumber } from '@ethersproject/bignumber' import { getAddress } from '@ethersproject/address' import { Contract } from '@ethersproject/contracts' +import { StreamPartDefinition } from './stream' // TODO get metadata type from streamr-protocol-js project (it doesn't export the type definitions yet) export type OnMessageCallback = MaybeAsync<(message: any, metadata: any) => void> +export type ResendOptions = { + from?: { timestamp: number, sequenceNumber?: number } + to?: { timestamp: number, sequenceNumber?: number } + last?: number +} + +export type SubscribeOptions = { + resend?: ResendOptions +} & ResendOptions + interface MessageEvent { data: any } @@ -29,10 +40,9 @@ interface MessageEvent { /** * Wrap connection message events with message parsing. */ - class StreamrConnection extends Connection { // TODO define args type when we convert Connection class to TypeScript - constructor(options: Todo, debug?: Debug.Debugger) { + constructor(options: ConnectionOptions, debug?: Debug.Debugger) { super(options, debug) this.on('message', this.onConnectionMessage) } @@ -160,6 +170,7 @@ export class StreamrClient extends EventEmitter { /** @internal */ ethereum: StreamrEthereum + // TODO annotate connection parameter as internal parameter if possible? constructor(options: StreamrClientOptions = {}, connection?: StreamrConnection) { super() this.id = counterId(`${this.constructor.name}:${uid}`) @@ -280,13 +291,13 @@ export class StreamrClient extends EventEmitter { ]) } - getSubscriptions(...args: Todo) { - return this.subscriber.getAll(...args) + getSubscriptions(): Subscription[] { + return this.subscriber.getAll() } - getSubscription(...args: Todo) { + getSubscription(definition: StreamPartDefinition) { // @ts-expect-error - return this.subscriber.get(...args) + return this.subscriber.get(definition) } async ensureConnected() { @@ -301,8 +312,8 @@ export class StreamrClient extends EventEmitter { return this.session.logout() } - async publish(...args: Todo) { - return this.publisher.publish(...args) + async publish(streamObjectOrId: StreamPartDefinition, content: object, timestamp?: number|string|Date, partitionKey?: string) { + return this.publisher.publish(streamObjectOrId, content, timestamp, partitionKey) } async getUserId() { @@ -319,7 +330,7 @@ export class StreamrClient extends EventEmitter { return this.publisher.rotateGroupKey(...args) } - async subscribe(opts: Todo, onMessage?: OnMessageCallback) { + async subscribe(opts: SubscribeOptions & StreamPartDefinition, onMessage?: OnMessageCallback) { let subTask: Todo let sub: Todo const hasResend = !!(opts.resend || opts.from || opts.to || opts.last) @@ -350,10 +361,11 @@ export class StreamrClient extends EventEmitter { return subTask } - async unsubscribe(opts: Todo) { - await this.subscriber.unsubscribe(opts) + async unsubscribe(subscription: Subscription) { + await this.subscriber.unsubscribe(subscription) } + /** @internal */ async resend(opts: Todo, onMessage?: OnMessageCallback): Promise { const task = this.subscriber.resend(opts) if (typeof onMessage !== 'function') { @@ -373,12 +385,12 @@ export class StreamrClient extends EventEmitter { return task } - enableAutoConnect(...args: Todo) { - return this.connection.enableAutoConnect(...args) + enableAutoConnect(autoConnect?: boolean) { + return this.connection.enableAutoConnect(autoConnect) } - enableAutoDisconnect(...args: Todo) { - return this.connection.enableAutoDisconnect(...args) + enableAutoDisconnect(autoDisconnect?: boolean) { + return this.connection.enableAutoDisconnect(autoDisconnect) } async getAddress(): Promise { diff --git a/src/rest/StreamEndpoints.ts b/src/rest/StreamEndpoints.ts index 7005f47f5..cd5a0ca08 100644 --- a/src/rest/StreamEndpoints.ts +++ b/src/rest/StreamEndpoints.ts @@ -11,7 +11,7 @@ import StreamPart from '../stream/StreamPart' import { isKeyExchangeStream } from '../stream/KeyExchange' import authFetch, { ErrorCode, NotFoundError } from './authFetch' -import { EthereumAddress, Todo } from '../types' +import { EthereumAddress } from '../types' import { StreamrClient } from '../StreamrClient' // TODO change this import when streamr-client-protocol exports StreamMessage type or the enums types directly import { ContentType, EncryptionType, SignatureType, StreamMessageType } from 'streamr-client-protocol/dist/src/protocol/message_layer/StreamMessage' @@ -226,6 +226,7 @@ export class StreamEndpoints { } async getStreamLast(streamObjectOrId: Stream|string): Promise { + // @ts-expect-error const { streamId, streamPartition = 0, count = 1 } = validateOptions(streamObjectOrId) this.client.debug('getStreamLast %o', { streamId, @@ -234,6 +235,7 @@ export class StreamEndpoints { }) const url = ( + // @ts-expect-error getEndpointUrl(this.client.options.restUrl, 'streams', streamId, 'data', 'partitions', streamPartition, 'last') + `?${qs.stringify({ count })}` ) @@ -252,7 +254,7 @@ export class StreamEndpoints { return result } - async publishHttp(streamObjectOrId: Stream|string, data: Todo, requestOptions: Todo = {}, keepAlive: boolean = true) { + async publishHttp(streamObjectOrId: Stream|string, data: any, requestOptions: any = {}, keepAlive: boolean = true) { let streamId if (streamObjectOrId instanceof Stream) { streamId = streamObjectOrId.id diff --git a/src/stream/index.ts b/src/stream/index.ts index 45c45465c..4476be363 100644 --- a/src/stream/index.ts +++ b/src/stream/index.ts @@ -3,7 +3,11 @@ import authFetch from '../rest/authFetch' import StorageNode from './StorageNode' import { StreamrClient } from '../StreamrClient' -import { Todo } from '../types' + +// TODO explicit types: e.g. we never provide both streamId and id, or both streamPartition and partition +export type StreamPartDefinition = string | { streamId?: string, streamPartition?: number, id?: string, partition?: number, stream?: Stream } + +export type ValidatedStreamPartDefinition = { streamId: string, streamPartition: number, key: string} interface StreamPermisionBase { id: number @@ -247,7 +251,7 @@ export class Stream { return json.map((item: any) => new StorageNode(item.storageNodeAddress)) } - async publish(...theArgs: Todo) { - return this._client.publish(this.id, ...theArgs) + async publish(content: object, timestamp?: number|string|Date, partitionKey?: string) { + return this._client.publish(this.id, content, timestamp, partitionKey) } } diff --git a/src/stream/utils.js b/src/stream/utils.ts similarity index 84% rename from src/stream/utils.js rename to src/stream/utils.ts index 03a3ad5df..91c716b7d 100644 --- a/src/stream/utils.js +++ b/src/stream/utils.ts @@ -7,8 +7,11 @@ import { inspect } from 'util' import { ControlLayer } from 'streamr-client-protocol' import { pTimeout } from '../utils' +import { Todo } from '../types' +import { StreamrClient } from '../StreamrClient' +import { StreamPartDefinition, ValidatedStreamPartDefinition } from '.' -export function StreamKey({ streamId, streamPartition = 0 }) { +export function StreamKey({ streamId, streamPartition = 0 }: Todo) { if (streamId == null) { throw new Error(`StreamKey: invalid streamId (${typeof streamId}): ${streamId}`) } if (!Number.isInteger(streamPartition) || streamPartition < 0) { @@ -17,13 +20,13 @@ export function StreamKey({ streamId, streamPartition = 0 }) { return `${streamId}::${streamPartition}` } -export function validateOptions(optionsOrStreamId) { +export function validateOptions(optionsOrStreamId: StreamPartDefinition): ValidatedStreamPartDefinition { if (!optionsOrStreamId) { throw new Error('streamId is required!') } // Backwards compatibility for giving a streamId as first argument - let options = {} + let options: Todo = {} if (typeof optionsOrStreamId === 'string') { options = { streamId: optionsOrStreamId, @@ -42,7 +45,7 @@ export function validateOptions(optionsOrStreamId) { options.streamId = optionsOrStreamId.id } - if (optionsOrStreamId.partition == null && optionsOrStreamId.streamPartition == null) { + if (optionsOrStreamId.partition != null && optionsOrStreamId.streamPartition == null) { options.streamPartition = optionsOrStreamId.partition } @@ -89,7 +92,7 @@ export async function waitForMatchingMessage({ rejectOnTimeout = true, timeoutMessage, cancelTask, -}) { +}: Todo) { if (typeof matchFn !== 'function') { throw new Error(`matchFn required, got: (${typeof matchFn}) ${matchFn}`) } @@ -98,7 +101,7 @@ export async function waitForMatchingMessage({ let cleanup = () => {} const matchTask = new Promise((resolve, reject) => { - const tryMatch = (...args) => { + const tryMatch = (...args: Todo[]) => { try { return matchFn(...args) } catch (err) { @@ -107,19 +110,20 @@ export async function waitForMatchingMessage({ return false } } - let onDisconnected - const onResponse = (res) => { + let onDisconnected: Todo + const onResponse = (res: Todo) => { if (!tryMatch(res)) { return } // clean up err handler cleanup() resolve(res) } - const onErrorResponse = (res) => { + const onErrorResponse = (res: Todo) => { if (!tryMatch(res)) { return } // clean up success handler cleanup() const error = new Error(res.errorMessage) + // @ts-expect-error error.code = res.errorCode reject(error) } @@ -128,12 +132,12 @@ export async function waitForMatchingMessage({ if (cancelTask) { cancelTask.catch(() => {}) } // ignore connection.off('disconnected', onDisconnected) connection.off(ControlMessage.TYPES.ErrorResponse, onErrorResponse) - types.forEach((type) => { + types.forEach((type: Todo) => { connection.off(type, onResponse) }) } - types.forEach((type) => { + types.forEach((type: Todo) => { connection.on(type, onResponse) }) @@ -141,6 +145,7 @@ export async function waitForMatchingMessage({ onDisconnected = () => { cleanup() + // @ts-expect-error resolve() // noop } @@ -171,7 +176,7 @@ export async function waitForMatchingMessage({ * Wait for matching response types to requestId, or ErrorResponse. */ -export async function waitForResponse({ requestId, timeoutMessage = `Waiting for response to: ${requestId}.`, ...opts }) { +export async function waitForResponse({ requestId, timeoutMessage = `Waiting for response to: ${requestId}.`, ...opts }: Todo) { if (requestId == null) { throw new Error(`requestId required, got: (${typeof requestId}) ${requestId}`) } @@ -180,13 +185,13 @@ export async function waitForResponse({ requestId, timeoutMessage = `Waiting for ...opts, requestId, timeoutMessage, - matchFn(res) { + matchFn(res: Todo) { return res.requestId === requestId } }) } -export async function waitForRequestResponse(client, request, opts = {}) { +export async function waitForRequestResponse(client: StreamrClient, request: Todo, opts: Todo = {}) { return waitForResponse({ connection: client.connection, types: PAIRS.get(request.type), diff --git a/src/subscribe/index.ts b/src/subscribe/index.ts index b7ce2e205..7cb1320f4 100644 --- a/src/subscribe/index.ts +++ b/src/subscribe/index.ts @@ -11,7 +11,7 @@ import Validator from './Validator' import messageStream from './messageStream' import resendStream from './resendStream' import { Todo } from '../types' -import StreamrClient from '..' +import StreamrClient, { StreamPartDefinition, SubscribeOptions } from '..' export class Subscription extends Emitter { @@ -84,7 +84,7 @@ export class Subscription extends Emitter { * Collect all messages into an array. * Returns array when subscription is ended. */ - async collect(n?: Todo) { + async collect(n?: number) { const msgs = [] for await (const msg of this) { if (n === 0) { @@ -126,8 +126,9 @@ export class Subscription extends Emitter { return this.pipeline.throw(...args) } - async unsubscribe(...args: Todo[]) { - return this.cancel(...args) + // TODO should we expose this to the user as no-args method? + async unsubscribe() { + return this.cancel() } } @@ -375,7 +376,7 @@ class Subscriptions { this.subSessions = new Map() } - async add(opts: Todo, onFinally: Todo = async () => {}) { + async add(opts: StreamPartDefinition, onFinally: Todo = async () => {}) { const options = validateOptions(opts) const { key } = options @@ -441,7 +442,7 @@ class Subscriptions { /** * Remove all subscriptions, optionally only those matching options. */ - async removeAll(options?: Todo) { + async removeAll(options?: StreamPartDefinition) { const subs = this.get(options) return allSettledValues(subs.map((sub: Todo) => ( this.remove(sub) @@ -464,7 +465,7 @@ class Subscriptions { * Count all matching subscriptions. */ - count(options: Todo) { + count(options?: StreamPartDefinition) { if (options === undefined) { return this.countAll() } return this.get(options).length } @@ -493,7 +494,7 @@ class Subscriptions { * Get all subscriptions matching options. */ - get(options: Todo) { + get(options?: StreamPartDefinition) { if (options === undefined) { return this.getAll() } const { key } = validateOptions(options) @@ -522,30 +523,31 @@ export class Subscriber { return this.subscriptions.getSubscriptionSession(...args) } - getAll(...args: Todo[]) { - // @ts-expect-error - return this.subscriptions.getAll(...args) + getAll() { + return this.subscriptions.getAll() } - count(options: Todo[]) { + count(options?: StreamPartDefinition) { return this.subscriptions.count(options) } - async subscribe(...args: Todo[]) { - // @ts-expect-error - return this.subscriptions.add(...args) + async subscribe(opts: StreamPartDefinition, onFinally?: Todo) { + return this.subscriptions.add(opts, onFinally) } - async unsubscribe(options: Todo): Promise { + async unsubscribe(options: Subscription | StreamPartDefinition | { options: Subscription|StreamPartDefinition }): Promise { if (options instanceof Subscription) { const sub = options return sub.cancel() } + // @ts-expect-error if (options && options.options) { + // @ts-expect-error return this.unsubscribe(options.options) } + // @ts-expect-error return this.subscriptions.removeAll(options) } @@ -564,12 +566,13 @@ export class Subscriber { return sub } - async resendSubscribe(opts: Todo, onMessage: Todo) { + async resendSubscribe(opts: SubscribeOptions & StreamPartDefinition, onMessage: Todo) { // This works by passing a custom message stream to a subscription // the custom message stream iterates resends, then iterates realtime const options = validateOptions(opts) const resendMessageStream = resendStream(this.client, options) + // @ts-expect-error const realtimeMessageStream = messageStream(this.client.connection, options) // cancel both streams on end @@ -644,6 +647,7 @@ export class Subscriber { const resendTask = resendMessageStream.subscribe() const realtimeTask = this.subscribe({ ...options, + // @ts-expect-error msgStream: it, afterSteps: [ async function* detectEndOfResend(src: Todo) { diff --git a/test/unit/StreamUtils.test.ts b/test/unit/StreamUtils.test.ts new file mode 100644 index 000000000..3cb5360aa --- /dev/null +++ b/test/unit/StreamUtils.test.ts @@ -0,0 +1,50 @@ +import { Stream } from '../../src/stream' +import { validateOptions } from '../../src/stream/utils' + +describe('Stream utils', () => { + + it('no definition', () => { + expect(() => validateOptions(undefined as any)).toThrow() + expect(() => validateOptions(null as any)).toThrow() + expect(() => validateOptions({})).toThrow() + }) + + it('string', () => { + expect(validateOptions('foo')).toMatchObject({ + streamId: 'foo', + streamPartition: 0, + key: 'foo::0' + }) + }) + + it('object', () => { + expect(validateOptions({ streamId: 'foo' })).toMatchObject({ + streamId: 'foo', + streamPartition: 0, + key: 'foo::0' + }) + expect(validateOptions({ streamId: 'foo', streamPartition: 123 })).toMatchObject({ + streamId: 'foo', + streamPartition: 123, + key: 'foo::123' + }) + expect(validateOptions({ id: 'foo', partition: 123 })).toMatchObject({ + streamId: 'foo', + streamPartition: 123, + key: 'foo::123' + }) + }) + + it('stream', () => { + const stream = new Stream(undefined as any, { + id: 'foo', + name: 'bar' + }) + expect(validateOptions({ stream })).toMatchObject({ + streamId: 'foo', + streamPartition: 0, + key: 'foo::0' + }) + }) + +}) diff --git a/test/utils.ts b/test/utils.ts index b691ff5ac..860c5fa9b 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -76,6 +76,7 @@ export function getWaitForStorage(client: StreamrClient, defaultOpts = {}) { /* eslint-disable no-await-in-loop */ return async (publishRequest: any, opts = {}) => { const { + // @ts-expect-error streamId, streamPartition = 0, interval = 500, timeout = 5000, count = 100, messageMatchFn = defaultMessageMatchFn } = validateOptions({ ...defaultOpts, @@ -145,15 +146,25 @@ export function getPublishTestMessages(client: StreamrClient, defaultOpts = {}) const { streamId, streamPartition = 0, + // @ts-expect-error delay = 100, + // @ts-expect-error timeout = 3500, + // @ts-expect-error waitForLast = false, // wait for message to hit storage + // @ts-expect-error waitForLastCount, + // @ts-expect-error waitForLastTimeout, + // @ts-expect-error beforeEach = (m: any) => m, + // @ts-expect-error afterEach = () => {}, + // @ts-expect-error timestamp, + // @ts-expect-error partitionKey, + // @ts-expect-error createMessage = () => { msgCount += 1 return {