UNPKG

vc-status-registry

Version:

Interact with a registry for credential statuses on Ethereum

563 lines (494 loc) 17.4 kB
/* * Copyright 2020 Coöperatieve Rabobank U.A. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import * as chai from 'chai' import * as sinon from 'sinon' import * as chaiAsPromised from 'chai-as-promised' import * as sinonChai from 'sinon-chai' import { ethers, Wallet } from 'ethers' import { TransactionCount, ContractEventData, NewBlockData, PastEventType, VcStatusRegistry } from '../src' const assert = chai.assert before(() => { chai.should() chai.use(chaiAsPromised) chai.use(sinonChai) }) // const privateKey = '53fd2ebe003d072fe914a90581b8d36964f2392ede2fab9618d4492cff85f35d'; // const issuer = '0xf8fC941a64cFb70C8E34010f7370D84076d0361A'; // const contractAddress = '0xe9fdf8130ad68fd11d195fb1e49a479e30b6d3d4'; // const credentialId = '0x72d3bc4da6e025540edbfee8b376918f95ec59e4'; const provider = 'https://rinkeby.infura.io/' describe('Test vcStatusRegistry functionality', () => { afterEach(() => { sinon.restore() }) it('should succesfully create vcStatusRegistry class with private key', () => { // Arrange const privateKey = '53fd2ebe003d072fe914a90581b8d36964f2392ede2fab9618d4492cff85f35d' const contractAddress = '0xe9fdf8130ad68fd11d195fb1e49a479e30b6d3d4' const options = { gasLimit: 220000, gasPrice: 111111, txNonceMaxRaceCount: 2, txNonceMaxIdleTime: 2 } // Act const vcStatusRegistry = new VcStatusRegistry( provider, contractAddress, privateKey, options ) // Assert assert.exists(vcStatusRegistry) }) it('should succesfully create vcStatusRegistry class without private key', () => { // Arrange const contractAddress = '0xe9fdf8130ad68fd11d195fb1e49a479e30b6d3d4' // Act const vcStatusRegistry = new VcStatusRegistry( provider, contractAddress ) // Assert assert.exists(vcStatusRegistry) }) it('should throw if function setVcStatus calls without instantiating with privateKey', () => { // Arrange const contractAddress = '0xe9fdf8130ad68fd11d195fb1e49a479e30b6d3d4' const vcStatusRegistry = new VcStatusRegistry( provider, contractAddress ) sinon .stub((vcStatusRegistry as any), '_contractMethod') .returns(Promise.resolve({ hash: 'dude' })) // Act return vcStatusRegistry.setVcStatus('credentialId').should.eventually.rejectedWith('Error: Can not call "setVcStatus" without privateKey') }) it('should throw if function removeVcStatus calls without instantiating with privateKey', () => { // Arrange const contractAddress = '0xe9fdf8130ad68fd11d195fb1e49a479e30b6d3d4' const vcStatusRegistry = new VcStatusRegistry( provider, contractAddress ) // Act return vcStatusRegistry.setVcStatus('credentialId', false).should.eventually.rejectedWith('Error: Can not call "removeVcStatus" without privateKey') }) it('should throw error if non existent ethereum-provider used', async () => { // Arrange const privateKey = '53fd2ebe003d072fe914a90581b8d36964f2392ede2fab9618d4492cff85f35d' const issuer = '0xf8fC941a64cFb70C8E34010f7370D84076d0361A' const contractAddress = '0xe9fdf8130ad68fd11d195fb1e49a479e30b6d3d4' const credentialId = '0x72d3bc4da6e025540edbfee8b376918f95ec59e4' // Act const vcStatusRegistry = new VcStatusRegistry( 'https://not-exists.infura.io', contractAddress, privateKey ) const wrapper = async () => { return vcStatusRegistry.getVcStatus(issuer, credentialId) } // Assert assert.isRejected(wrapper(), 'Cannot read property \'getVcStatus\' of undefined') }) it('should not add a value to the registry if not initialized with privateKey', async () => { // Arrange const contractAddress = '0xe9fdf8130ad68fd11d195fb1e49a479e30b6d3d4' const credentialId = '0x72d3bc4da6e025540edbfee8b376918f95ec59e4' // Act const vcStatusRegistry = new VcStatusRegistry( provider, contractAddress ) // Assert vcStatusRegistry.setVcStatus(credentialId, false).should.eventually.be.rejectedWith('Error: Can not call "removeVcStatus" without privateKey') }) // Arrange const issuer = '0xf8fC941a64cFb70C8E34010f7370D84076d0361A' const privateKey = '53fd2ebe003d072fe914a90581b8d36964f2392ede2fab9618d4492cff85f35d' const contractAddress = '0xe9fdf8130ad68fd11d195fb1e49a479e30b6d3d4' const credentialId1 = '0xf8fC941a64cFb70C8E34010f7370D84076d0361A' const credentialId2 = '0xe9fdf8130ad68fd11d195fb1e49a479e30b6d3d4' const credentialId3 = '0x72d3bc4da6e025540edbfee8b376918f95ec59e4' const vcStatusRegistry = new VcStatusRegistry( provider, contractAddress, privateKey ) it('should return wallet', async () => { const result = vcStatusRegistry.wallet as Wallet assert.deepEqual(result.address, '0xac9E10ab57f6DcDad1bA0C634EdCAE25116F5dab') assert.deepEqual(result.privateKey, '0x' + privateKey) }) it('should return provider', async () => { const result = vcStatusRegistry.provider assert.isObject(result) }) it('should return ethereumProvider', async () => { const result = vcStatusRegistry.ethereumProvider assert.deepEqual(result, provider) }) it('should return contractAddress', () => { const result = vcStatusRegistry.contractAddress assert.deepEqual(result, contractAddress) }) it('should succesfully getVcStatus function()', async () => { const stubGetVcStatus = sinon .stub(vcStatusRegistry as any, 'getVcStatus') .returns(Promise.resolve('true')) const result = await vcStatusRegistry.getVcStatus(issuer, credentialId3) // Assert assert.isTrue(stubGetVcStatus.calledOnce) assert.isTrue(Boolean(result)) }) it('should add values to the registry', async () => { const stubContractMethod = sinon .stub((vcStatusRegistry as any), '_contractMethod') .returns(Promise.resolve({ hash: 'dude' })) sinon .stub((vcStatusRegistry as any)._wallet, 'getTransactionCount') .returns(Promise.resolve(31415)) // Call it twice so 'racing' is also tested const res = await Promise.all([ vcStatusRegistry.setVcStatus(credentialId1), vcStatusRegistry.setVcStatus(credentialId2) ]) // Assert assert.isTrue(stubContractMethod.calledTwice) assert.deepEqual(res, ['dude', 'dude']) }) it('should remove a value from the registry', async () => { const stubContractMethod = sinon .stub(vcStatusRegistry as any, '_contractMethod') .returns(Promise.resolve({ hash: 'dude' })) sinon .stub((vcStatusRegistry as any)._wallet, 'getTransactionCount') .returns(Promise.resolve(31415)) await vcStatusRegistry.setVcStatus(credentialId3, false) // Assert assert.isTrue(stubContractMethod.calledOnce) }) const vcStatusRegistryKeyless = new VcStatusRegistry( provider, contractAddress ) it('should throw if setVcStatus is called without privateKey', async () => { sinon .stub(vcStatusRegistry as any, '_contractMethod') .returns(Promise.resolve({ hash: 'dude' })) sinon .stub((vcStatusRegistry as any)._wallet, 'getTransactionCount') .returns(Promise.resolve(31415)) const wrapper = async () => { return vcStatusRegistryKeyless.setVcStatus(credentialId1) } // Assert assert.isRejected(wrapper(), 'Error: Can not call "setVcStatus" without privateKey') }) it('should create and get a transaction count', async () => { const options = { txNonceMaxRaceCount: 5, txNonceMaxIdleTime: 30000 } const wallet = new Wallet(Buffer.from(privateKey, 'hex'), vcStatusRegistry.provider) const transactionCount = new TransactionCount(wallet, options) const stubTransactionCount = sinon .stub(wallet, 'getTransactionCount') .returns(Promise.resolve(0)) const count1 = await transactionCount.transactionCount() const count2 = await transactionCount.transactionCount() const count3 = await transactionCount.transactionCount() assert.deepEqual(count1, 0) assert.deepEqual(count2, 1) assert.deepEqual(count3, 2) assert.isTrue(stubTransactionCount.calledThrice) }) it('should reset the transaction count because of maxRaceCount', async () => { const options = { txNonceMaxRaceCount: 1, txNonceMaxIdleTime: 30000 } const wallet = new Wallet(Buffer.from(privateKey, 'hex'), vcStatusRegistry.provider) const transactionCount = new TransactionCount(wallet, options) const stubTransactionCount = sinon .stub(wallet, 'getTransactionCount') .returns(Promise.resolve(0)) const count1 = await transactionCount.transactionCount() const count2 = await transactionCount.transactionCount() const count3 = await transactionCount.transactionCount() assert.deepEqual(count1, 0) assert.deepEqual(count2, 1) assert.deepEqual(count3, 0) assert.isTrue(stubTransactionCount.calledThrice) }) it('should reset the transaction count because of maxIdleTime', async () => { const options = { txNonceMaxRaceCount: 5, txNonceMaxIdleTime: 0 } const wallet = new Wallet(Buffer.from(privateKey, 'hex'), vcStatusRegistry.provider) const transactionCount = new TransactionCount(wallet, options) const stubTransactionCount = sinon .stub(wallet, 'getTransactionCount') .returns(Promise.resolve(0)) const count1 = await transactionCount.transactionCount() const count2 = await transactionCount.transactionCount() const count3 = await transactionCount.transactionCount() assert.deepEqual(count1, 0) assert.deepEqual(count2, 0) assert.deepEqual(count3, 0) assert.isTrue(stubTransactionCount.calledThrice) }) it('should return exactly this ABI', () => { const ABI = [ { 'constant': false, 'inputs': [ { 'name': 'credentialId', 'type': 'address' } ], 'name': 'setVcStatus', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'constant': false, 'inputs': [ { 'name': 'credentialId', 'type': 'address' } ], 'name': 'removeVcStatus', 'outputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function' }, { 'constant': true, 'inputs': [ { 'name': 'issuer', 'type': 'address' }, { 'name': 'credentialId', 'type': 'address' } ], 'name': 'getVcStatus', 'outputs': [ { 'name': '', 'type': 'bool' } ], 'payable': false, 'stateMutability': 'view', 'type': 'function' }, { 'anonymous': false, 'inputs': [ { 'indexed': true, 'name': 'issuer', 'type': 'address' }, { 'indexed': true, 'name': 'credentialId', 'type': 'address' } ], 'name': 'VcStatusSet', 'type': 'event' }, { 'anonymous': false, 'inputs': [ { 'indexed': true, 'name': 'issuer', 'type': 'address' }, { 'indexed': true, 'name': 'credentialId', 'type': 'address' } ], 'name': 'VcStatusRemoved', 'type': 'event' } ] const contractAddress = '0xe9fdf8130ad68fd11d195fb1e49a479e30b6d3d4' const vcStatusRegistry = new VcStatusRegistry( provider, contractAddress ) // Act assert.deepEqual(vcStatusRegistry.ABI, ABI) }) it('should forward the event when SetVcStatus happens on the contract', async () => { const providerData = { blockNumber: 12, blockHash: 'someHash', transactionIndex: 12, removed: true, address: '0xasd', data: 'SomeDataHere', topics: ['0xa', '0xb'], transactionHash: 'hashControl', logIndex: 54 } const statusSetFilter = { address: contractAddress, topics: [ethers.utils.id('VcStatusSet(address,address)')] } let returnData: ContractEventData vcStatusRegistry.onSetVcStatus.subscribe(async (e: ContractEventData) => { returnData = e }) vcStatusRegistry.provider.emit(statusSetFilter, providerData) setTimeout(() => { assert.deepEqual(returnData, providerData) },1) }) it('should forward the event when RemoveVcStatus happens on the contract', async () => { const providerData = { blockNumber: 12, blockHash: 'someHash', transactionIndex: 12, removed: true, address: '0xasd', data: 'HelloDataHere', topics: ['0xa', '0xb'], transactionHash: 'hashControl', logIndex: 54 } const statusSetFilter = { address: contractAddress, topics: [ethers.utils.id('VcStatusRemoved(address,address)')] } let returnData: ContractEventData vcStatusRegistry.onRemoveVcStatus.subscribe(async (e: ContractEventData) => { returnData = e }) vcStatusRegistry.provider.emit(statusSetFilter, providerData) setTimeout(() => { assert.deepEqual(returnData, providerData) },1) }) it('should forward the event when newBlock happens on the blockchain', async () => { let returnData: NewBlockData vcStatusRegistry.onNewBlock.subscribe(async (e: NewBlockData) => { returnData = e }) vcStatusRegistry.provider.emit('block', 12) setTimeout(() => { assert.deepEqual(returnData, { blockNumber: 12 }) },1) }) it('should forward the error when it happens on the blockchain', async () => { let returnData: any vcStatusRegistry.onError.subscribe(async (e: any) => { returnData = e }) vcStatusRegistry.provider.emit('error', { some: 'error' }) setTimeout(() => { assert.deepEqual(returnData, { some: 'error' }) },1) }) it('return the current blockNumber when asked', async () => { sinon.stub(vcStatusRegistry.provider, 'getBlockNumber').returns(Promise.resolve(54)) const blockNumber = await vcStatusRegistry.getBlockNumber() assert.equal(blockNumber, 54) }) it('return the past events for SetStatus event when asked with fromBlock and toBlock', async () => { let sinonStub = sinon.stub(vcStatusRegistry.provider, 'getLogs') sinonStub.returns(Promise.resolve([{ blockNumber: 1, blockHash: 'hash', transactionIndex: 1, removed: false, address: 'adress', data: 'data', topics: ['topic1', 'topic2'], transactionHash: '2', logIndex: 1 }])) const pastEvents = await vcStatusRegistry.getPastStatusEvents(PastEventType.set, 'did', 1, 2) assert.equal(pastEvents.length, 1) assert.isTrue(sinonStub.calledWithExactly({ address: contractAddress, fromBlock: 1, toBlock: 2, topics: [ethers.utils.id('VcStatusSet(address,address)'), [], 'did'] })) }) it('return the past events for SetStatus event when asked with default fromBlock and toBlock', async () => { let sinonStub = sinon.stub(vcStatusRegistry.provider, 'getLogs') sinonStub.returns(Promise.resolve([{ blockNumber: 1, blockHash: 'hash', transactionIndex: 1, removed: false, address: 'adress', data: 'data', topics: ['topic1', 'topic2'], transactionHash: '2', logIndex: 1 }])) const pastEvents = await vcStatusRegistry.getPastStatusEvents(PastEventType.set, 'did') assert.equal(pastEvents.length, 1) assert.isTrue(sinonStub.calledWithExactly({ address: contractAddress, fromBlock: 0, toBlock: 'latest', topics: [ethers.utils.id('VcStatusSet(address,address)'), [], 'did'] })) }) it('return the past events for RemovedStatus event when asked with default fromBlock and toBlock', async () => { let sinonStub = sinon.stub(vcStatusRegistry.provider, 'getLogs') sinonStub.returns(Promise.resolve([{ blockNumber: 1, blockHash: 'hash', transactionIndex: 1, removed: false, address: 'adress', data: 'data', topics: ['topic1', 'topic2'], transactionHash: '2', logIndex: 1 }])) const pastEvents = await vcStatusRegistry.getPastStatusEvents(PastEventType.remove, 'did') assert.equal(pastEvents.length, 1) assert.isTrue(sinonStub.calledWithExactly({ address: contractAddress, fromBlock: 0, toBlock: 'latest', topics: [ethers.utils.id('VcStatusRemoved(address,address)'), [], 'did'] })) }) })