diff --git a/package-lock.json b/package-lock.json index ad49607befd..49f624f1720 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1499,6 +1499,10 @@ "tweetnacl": "^0.14.3" } }, + "bignumber.js": { + "version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", + "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git" + }, "binary-extensions": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", @@ -2505,12 +2509,13 @@ "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-2.0.1.tgz", "integrity": "sha512-lxHZOQspexk3DaGj4RBbWy4C/qNOWRnxpaJzNnYD3WEmC8shcJ4tHs7Xv878rzvILfJnSFSCCiKQhng1m80oBQ==", "requires": { + "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", "ethereumjs-util": "^5.1.1" }, "dependencies": { "ethereumjs-abi": { "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", - "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", + "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "requires": { "bn.js": "^4.10.0", "ethereumjs-util": "^5.0.0" @@ -2574,12 +2579,13 @@ "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz", "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", "requires": { + "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", "ethereumjs-util": "^5.1.1" }, "dependencies": { "ethereumjs-abi": { "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", - "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", + "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "requires": { "bn.js": "^4.10.0", "ethereumjs-util": "^5.0.0" @@ -2652,12 +2658,13 @@ "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-2.0.1.tgz", "integrity": "sha512-lxHZOQspexk3DaGj4RBbWy4C/qNOWRnxpaJzNnYD3WEmC8shcJ4tHs7Xv878rzvILfJnSFSCCiKQhng1m80oBQ==", "requires": { + "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", "ethereumjs-util": "^5.1.1" }, "dependencies": { "ethereumjs-abi": { "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", - "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", + "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "requires": { "bn.js": "^4.10.0", "ethereumjs-util": "^5.0.0" @@ -2773,7 +2780,18 @@ "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz", "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", "requires": { + "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", "ethereumjs-util": "^5.1.1" + }, + "dependencies": { + "ethereumjs-abi": { + "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", + "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", + "requires": { + "bn.js": "^4.10.0", + "ethereumjs-util": "^5.0.0" + } + } } }, "ethereumjs-abi": { @@ -3357,15 +3375,13 @@ "version": "1.0.0", "resolved": false, "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": false, "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3382,22 +3398,19 @@ "version": "1.1.0", "resolved": false, "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "resolved": false, "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "resolved": false, "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -3528,8 +3541,7 @@ "version": "2.0.3", "resolved": false, "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -3543,7 +3555,6 @@ "resolved": false, "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -3560,7 +3571,6 @@ "resolved": false, "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -3569,15 +3579,13 @@ "version": "0.0.8", "resolved": false, "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.2.4", "resolved": false, "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -3598,7 +3606,6 @@ "resolved": false, "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3687,8 +3694,7 @@ "version": "1.0.1", "resolved": false, "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3702,7 +3708,6 @@ "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -3840,7 +3845,6 @@ "resolved": false, "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -9624,16 +9628,11 @@ "resolved": "https://registry.npmjs.org/web3/-/web3-0.20.7.tgz", "integrity": "sha512-VU6/DSUX93d1fCzBz7WP/SGCQizO1rKZi4Px9j/3yRyfssHyFcZamMw2/sj4E8TlfMXONvZLoforR8B4bRoyTQ==", "requires": { + "bignumber.js": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", "crypto-js": "^3.1.4", "utf8": "^2.1.1", "xhr2-cookies": "^1.1.0", "xmlhttprequest": "*" - }, - "dependencies": { - "bignumber.js": { - "version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", - "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934" - } } }, "web3-provider-engine": { @@ -9683,12 +9682,13 @@ "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz", "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", "requires": { + "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", "ethereumjs-util": "^5.1.1" }, "dependencies": { "ethereumjs-abi": { "version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", - "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799", + "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git", "requires": { "bn.js": "^4.10.0", "ethereumjs-util": "^5.0.0" diff --git a/src/AssetsContractController.test.ts b/src/AssetsContractController.test.ts index 1531f71eb65..c9107f7b3c2 100644 --- a/src/AssetsContractController.test.ts +++ b/src/AssetsContractController.test.ts @@ -28,7 +28,7 @@ describe('AssetsContractController', () => { it('should get balance of contract correctly', async () => { assetsContract.configure({ provider: MAINNET_PROVIDER }); const CKBalance = await assetsContract.getBalanceOf(CKADDRESS, '0xb1690c08e213a35ed9bab7b318de14420fb57d8c'); - const CKNoBalance = await assetsContract.getBalanceOf(CKADDRESS, '0xfoO'); + const CKNoBalance = await assetsContract.getBalanceOf(CKADDRESS, '0xb1690c08e213a35ed9bab7b318de14420fb57d81'); expect(CKBalance.toNumber()).not.toEqual(0); expect(CKNoBalance.toNumber()).toEqual(0); }); diff --git a/src/AssetsContractController.ts b/src/AssetsContractController.ts index d4eefd020a6..0379abfbf5d 100644 --- a/src/AssetsContractController.ts +++ b/src/AssetsContractController.ts @@ -34,27 +34,17 @@ export class AssetsContractController extends BaseController { - /* istanbul ignore if */ - if (!this.web3) { - return false; - } - try { - const contract = this.web3.eth.contract(abiERC721).at(address); - return await new Promise((resolve, reject) => { - contract.supportsInterface(interfaceId, (error: Error, result: boolean) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result); - }); + const contract = this.web3.eth.contract(abiERC721).at(address); + return new Promise((resolve, reject) => { + contract.supportsInterface(interfaceId, (error: Error, result: boolean) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result); }); - } catch (error) { - /* istanbul ignore next */ - /* waiting for https://github.com/ethereum/web3.js/issues/1119 */ - return false; - } + }); } /** @@ -110,30 +100,20 @@ export class AssetsContractController extends BaseController { - /* istanbul ignore if */ - if (!this.web3) { - return new BN(0); - } - try { - const contract = this.web3.eth.contract(abiERC20).at(address); - return await new Promise((resolve, reject) => { - contract.balanceOf(selectedAddress, (error: Error, result: typeof BN) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result); - }); + const contract = this.web3.eth.contract(abiERC20).at(address); + return new Promise((resolve, reject) => { + contract.balanceOf(selectedAddress, (error: Error, result: typeof BN) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result); }); - } catch (error) { - /* istanbul ignore next */ - /* waiting for https://github.com/ethereum/web3.js/issues/1119 */ - return new BN(0); - } + }); } /** @@ -166,27 +146,17 @@ export class AssetsContractController extends BaseController { - /* istanbul ignore if */ - if (!this.web3) { - return ''; - } - try { - const contract = this.web3.eth.contract(abiERC721).at(address); - return await new Promise((resolve, reject) => { - contract.tokenURI(tokenId, (error: Error, result: string) => { - /* istanbul ignore if */ - if (error) { - reject(error); - return; - } - resolve(result); - }); + const contract = this.web3.eth.contract(abiERC721).at(address); + return new Promise((resolve, reject) => { + contract.tokenURI(tokenId, (error: Error, result: string) => { + /* istanbul ignore if */ + if (error) { + reject(error); + return; + } + resolve(result); }); - } catch (error) { - /* istanbul ignore next */ - /* waiting for https://github.com/ethereum/web3.js/issues/1119 */ - return ''; - } + }); } } diff --git a/src/AssetsController.test.ts b/src/AssetsController.test.ts index 17aca8a4a3e..b87077955f8 100644 --- a/src/AssetsController.test.ts +++ b/src/AssetsController.test.ts @@ -1,4 +1,4 @@ -import { stub } from 'sinon'; +import { createSandbox } from 'sinon'; import { getOnce } from 'fetch-mock'; import AssetsController from './AssetsController'; import ComposableController from './ComposableController'; @@ -18,12 +18,19 @@ describe('AssetsController', () => { let preferences: PreferencesController; let network: NetworkController; let assetsContract: AssetsContractController; + const sandbox = createSandbox(); beforeEach(() => { assetsController = new AssetsController(); preferences = new PreferencesController(); network = new NetworkController(); assetsContract = new AssetsContractController(); + /* tslint:disable-next-line:no-unused-expression */ + new ComposableController([assetsController, assetsContract, network, preferences]); + }); + + afterEach(() => { + sandbox.reset(); }); it('should set default state', () => { @@ -35,14 +42,14 @@ describe('AssetsController', () => { }); }); - it('should add token', () => { - assetsController.addToken('foo', 'bar', 2); + it('should add token', async () => { + await assetsController.addToken('foo', 'bar', 2); expect(assetsController.state.tokens[0]).toEqual({ address: '0xfoO', decimals: 2, symbol: 'bar' }); - assetsController.addToken('foo', 'baz', 2); + await assetsController.addToken('foo', 'baz', 2); expect(assetsController.state.tokens[0]).toEqual({ address: '0xfoO', decimals: 2, @@ -50,13 +57,12 @@ describe('AssetsController', () => { }); }); - it('should add token by selected address', () => { + it('should add token by selected address', async () => { const firstAddress = '0x123'; const secondAddress = '0x321'; - /* tslint:disable-next-line:no-unused-expression */ - new ComposableController([assetsController, assetsContract, network, preferences]); + preferences.update({ selectedAddress: firstAddress }); - assetsController.addToken('foo', 'bar', 2); + await assetsController.addToken('foo', 'bar', 2); preferences.update({ selectedAddress: secondAddress }); expect(assetsController.state.tokens.length).toEqual(0); preferences.update({ selectedAddress: firstAddress }); @@ -67,13 +73,11 @@ describe('AssetsController', () => { }); }); - it('should add token by provider type', () => { + it('should add token by provider type', async () => { const firstNetworkType = 'rinkeby'; const secondNetworkType = 'ropsten'; - /* tslint:disable-next-line:no-unused-expression */ - new ComposableController([assetsController, assetsContract, network, preferences]); network.update({ provider: { type: firstNetworkType } }); - assetsController.addToken('foo', 'bar', 2); + await assetsController.addToken('foo', 'bar', 2); network.update({ provider: { type: secondNetworkType } }); expect(assetsController.state.tokens.length).toEqual(0); network.update({ provider: { type: firstNetworkType } }); @@ -84,21 +88,19 @@ describe('AssetsController', () => { }); }); - it('should remove token', () => { - assetsController.addToken('foo', 'bar', 2); + it('should remove token', async () => { + await assetsController.addToken('foo', 'bar', 2); assetsController.removeToken('0xfoO'); expect(assetsController.state.tokens.length).toBe(0); }); - it('should remove token by selected address', () => { + it('should remove token by selected address', async () => { const firstAddress = '0x123'; const secondAddress = '0x321'; - /* tslint:disable-next-line:no-unused-expression */ - new ComposableController([assetsController, assetsContract, network, preferences]); preferences.update({ selectedAddress: firstAddress }); - assetsController.addToken('fou', 'baz', 2); + await assetsController.addToken('fou', 'baz', 2); preferences.update({ selectedAddress: secondAddress }); - assetsController.addToken('foo', 'bar', 2); + await assetsController.addToken('foo', 'bar', 2); assetsController.removeToken('0xfoO'); expect(assetsController.state.tokens.length).toEqual(0); preferences.update({ selectedAddress: firstAddress }); @@ -109,15 +111,13 @@ describe('AssetsController', () => { }); }); - it('should remove token by provider type', () => { + it('should remove token by provider type', async () => { const firstNetworkType = 'rinkeby'; const secondNetworkType = 'ropsten'; - /* tslint:disable-next-line:no-unused-expression */ - new ComposableController([assetsController, assetsContract, network, preferences]); network.update({ provider: { type: firstNetworkType } }); - assetsController.addToken('fou', 'baz', 2); + await assetsController.addToken('fou', 'baz', 2); network.update({ provider: { type: secondNetworkType } }); - assetsController.addToken('foo', 'bar', 2); + await assetsController.addToken('foo', 'bar', 2); assetsController.removeToken('0xfoO'); expect(assetsController.state.tokens.length).toEqual(0); network.update({ provider: { type: firstNetworkType } }); @@ -129,8 +129,6 @@ describe('AssetsController', () => { }); it('should add collectible', async () => { - /* tslint:disable-next-line:no-unused-expression */ - new ComposableController([assetsController, assetsContract, network, preferences]); assetsContract.configure({ provider: MAINNET_PROVIDER }); await assetsController.addCollectible('foo', 1234); expect(assetsController.state.collectibles).toEqual([ @@ -144,8 +142,6 @@ describe('AssetsController', () => { }); it('should add collectible with enumerable support but no tokenURI', async () => { - /* tslint:disable-next-line:no-unused-expression */ - new ComposableController([assetsController, assetsContract, network, preferences]); assetsContract.configure({ provider: MAINNET_PROVIDER }); await assetsController.addCollectible('0x8c9b261Faef3b3C2e64ab5E58e04615F8c788099', 1); expect(assetsController.state.collectibles).toEqual([ @@ -162,8 +158,6 @@ describe('AssetsController', () => { getOnce('https://api.godsunchained.com/card/1', () => ({ body: JSON.stringify({ image: 'https://api.godsunchained.com/v0/image/7', name: 'Broken Harvester' }) })); - /* tslint:disable-next-line:no-unused-expression */ - new ComposableController([assetsController, assetsContract, network, preferences]); assetsContract.configure({ provider: MAINNET_PROVIDER }); await assetsController.addCollectible(GODSADDRESS, 1); expect(assetsController.state.collectibles).toEqual([ @@ -184,8 +178,6 @@ describe('AssetsController', () => { name: 'Genesis' }) })); - /* tslint:disable-next-line:no-unused-expression */ - new ComposableController([assetsController, assetsContract, network, preferences]); assetsContract.configure({ provider: MAINNET_PROVIDER }); await assetsController.addCollectible(CKADDRESS, 1); expect(assetsController.state.collectibles).toEqual([ @@ -201,9 +193,9 @@ describe('AssetsController', () => { it('should add collectible by selected address', async () => { const firstAddress = '0x123'; const secondAddress = '0x321'; - stub(assetsController, 'getCollectibleCustomInformation' as any).returns({ name: 'name', image: 'url' }); - /* tslint:disable-next-line:no-unused-expression */ - new ComposableController([assetsController, assetsContract, network, preferences]); + sandbox + .stub(assetsController, 'getCollectibleCustomInformation' as any) + .returns({ name: 'name', image: 'url' }); preferences.update({ selectedAddress: firstAddress }); await assetsController.addCollectible('foo', 1234); preferences.update({ selectedAddress: secondAddress }); @@ -220,9 +212,9 @@ describe('AssetsController', () => { it('should add collectible by provider type', async () => { const firstNetworkType = 'rinkeby'; const secondNetworkType = 'ropsten'; - stub(assetsController, 'getCollectibleCustomInformation' as any).returns({ name: 'name', image: 'url' }); - /* tslint:disable-next-line:no-unused-expression */ - new ComposableController([assetsController, assetsContract, network, preferences]); + sandbox + .stub(assetsController, 'getCollectibleCustomInformation' as any) + .returns({ name: 'name', image: 'url' }); network.update({ provider: { type: firstNetworkType } }); await assetsController.addCollectible('foo', 1234); network.update({ provider: { type: secondNetworkType } }); @@ -236,19 +228,21 @@ describe('AssetsController', () => { }); }); - it('should remove collectible', () => { - stub(assetsController, 'getCollectibleCustomInformation' as any).returns({ name: 'name', image: 'url' }); - assetsController.addCollectible('0xfoO', 1234); + it('should remove collectible', async () => { + sandbox + .stub(assetsController, 'getCollectibleCustomInformation' as any) + .returns({ name: 'name', image: 'url' }); + await assetsController.addCollectible('0xfoO', 1234); assetsController.removeCollectible('0xfoO', 1234); expect(assetsController.state.collectibles.length).toBe(0); }); it('should remove collectible by selected address', async () => { - stub(assetsController, 'getCollectibleCustomInformation' as any).returns({ name: 'name', image: 'url' }); + sandbox + .stub(assetsController, 'getCollectibleCustomInformation' as any) + .returns({ name: 'name', image: 'url' }); const firstAddress = '0x123'; const secondAddress = '0x321'; - /* tslint:disable-next-line:no-unused-expression */ - new ComposableController([assetsController, assetsContract, network, preferences]); preferences.update({ selectedAddress: firstAddress }); await assetsController.addCollectible('fou', 4321); preferences.update({ selectedAddress: secondAddress }); @@ -265,11 +259,11 @@ describe('AssetsController', () => { }); it('should remove collectible by provider type', async () => { - stub(assetsController, 'getCollectibleCustomInformation' as any).returns({ name: 'name', image: 'url' }); + sandbox + .stub(assetsController, 'getCollectibleCustomInformation' as any) + .returns({ name: 'name', image: 'url' }); const firstNetworkType = 'rinkeby'; const secondNetworkType = 'ropsten'; - /* tslint:disable-next-line:no-unused-expression */ - new ComposableController([assetsController, assetsContract, network, preferences]); network.update({ provider: { type: firstNetworkType } }); await assetsController.addCollectible('fou', 4321); network.update({ provider: { type: secondNetworkType } }); @@ -287,7 +281,7 @@ describe('AssetsController', () => { }); it('should not add duplicated collectible', async () => { - const func = stub(assetsController, 'getCollectibleCustomInformation' as any).returns({ + const func = sandbox.stub(assetsController, 'getCollectibleCustomInformation' as any).returns({ image: 'url', name: 'name' }); @@ -324,8 +318,6 @@ describe('AssetsController', () => { it('should subscribe to new sibling preference controllers', async () => { const networkType = 'rinkeby'; const address = '0x123'; - /* tslint:disable-next-line:no-unused-expression */ - new ComposableController([assetsController, assetsContract, network, preferences]); preferences.update({ selectedAddress: address }); expect(assetsController.context.PreferencesController.state.selectedAddress).toEqual(address); network.update({ provider: { type: networkType } }); @@ -333,9 +325,11 @@ describe('AssetsController', () => { }); it('should return correct assets state', async () => { - stub(assetsController, 'getCollectibleCustomInformation' as any).returns({ name: 'name', image: 'url' }); + sandbox + .stub(assetsController, 'getCollectibleCustomInformation' as any) + .returns({ name: 'name', image: 'url' }); await assetsController.addCollectible('foo', 1234); - assetsController.addToken('foo', 'bar', 2); + await assetsController.addToken('foo', 'bar', 2); expect(assetsController.state.tokens).toEqual(TOKENS); expect(assetsController.state.collectibles).toEqual(COLLECTIBLES); }); diff --git a/src/AssetsController.ts b/src/AssetsController.ts index 982d0aa6d29..1b7669b8c04 100644 --- a/src/AssetsController.ts +++ b/src/AssetsController.ts @@ -8,6 +8,7 @@ import { manageCollectibleImage } from './util'; const contractMap = require('eth-contract-metadata'); const { toChecksumAddress } = require('ethereumjs-util'); +const Mutex = require('await-semaphore').Mutex; /** * @type Collectible @@ -102,6 +103,7 @@ export interface AssetsState extends BaseState { * Controller that stores assets and exposes convenience methods */ export class AssetsController extends BaseController { + private mutex = new Mutex(); /** * Get collectible tokenURI API * @@ -119,7 +121,8 @@ export class AssetsController extends BaseController if (!supportsMetadata) { return ''; } - return await assetsContract.getCollectibleTokenURI(contract.address, tokenId); + const tokenURI = await assetsContract.getCollectibleTokenURI(contract.address, tokenId); + return tokenURI; } /** @@ -170,7 +173,7 @@ export class AssetsController extends BaseController constructor(config?: Partial, state?: Partial) { super(config, state); this.defaultConfig = { - networkType: 'rinkeby', + networkType: 'ropsten', selectedAddress: '' }; this.defaultState = { @@ -190,7 +193,8 @@ export class AssetsController extends BaseController * @param decimals - Number of decimals the token uses * @returns - Current token list */ - addToken(address: string, symbol: string, decimals: number) { + async addToken(address: string, symbol: string, decimals: number) { + const releaseLock = await this.mutex.acquire(); address = toChecksumAddress(address); const { allTokens, tokens } = this.state; const { networkType, selectedAddress } = this.config; @@ -207,6 +211,7 @@ export class AssetsController extends BaseController const newAllTokens = { ...allTokens, ...{ [selectedAddress]: newAddressTokens } }; const newTokens = [...tokens]; this.update({ allTokens: newAllTokens, tokens: newTokens }); + releaseLock(); return newTokens; } @@ -218,6 +223,7 @@ export class AssetsController extends BaseController * @returns - Promise resolving to the current collectible list */ async addCollectible(address: string, tokenId: number): Promise { + const releaseLock = await this.mutex.acquire(); address = toChecksumAddress(address); const { allCollectibles, collectibles } = this.state; const { networkType, selectedAddress } = this.config; @@ -225,6 +231,7 @@ export class AssetsController extends BaseController (collectible) => collectible.address === address && collectible.tokenId === tokenId ); if (existingEntry) { + releaseLock(); return collectibles; } const { name, image } = await this.getCollectibleCustomInformation(address, tokenId); @@ -234,6 +241,7 @@ export class AssetsController extends BaseController const newAddressCollectibles = { ...addressCollectibles, ...{ [networkType]: newCollectibles } }; const newAllCollectibles = { ...allCollectibles, ...{ [selectedAddress]: newAddressCollectibles } }; this.update({ allCollectibles: newAllCollectibles, collectibles: newCollectibles }); + releaseLock(); return newCollectibles; } diff --git a/src/AssetsDetectionController.test.ts b/src/AssetsDetectionController.test.ts index 5eb8dce69dd..de967bbda81 100644 --- a/src/AssetsDetectionController.test.ts +++ b/src/AssetsDetectionController.test.ts @@ -10,6 +10,7 @@ import { AssetsContractController } from './AssetsContractController'; const BN = require('ethereumjs-util').BN; const HttpProvider = require('ethjs-provider-http'); const DEFAULT_INTERVAL = 180000; +const MAINNET = 'mainnet'; const MAINNET_PROVIDER = new HttpProvider('https://mainnet.infura.io'); const GODSADDRESS = '0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab'; const CKADDRESS = '0x06012c8cf97BEaD5deAe237070F9587f8E7A266d'; @@ -34,13 +35,13 @@ describe('AssetsDetectionController', () => { }); afterEach(() => { - sandbox.restore(); + sandbox.reset(); }); it('should set default config', () => { expect(assetsDetection.config).toEqual({ interval: DEFAULT_INTERVAL, - providerType: '', + networkType: 'ropsten', selectedAddress: '', tokens: [] }); @@ -56,7 +57,7 @@ describe('AssetsDetectionController', () => { it('should poll and detect assets on interval while mainnet', () => { const clock = sandbox.useFakeTimers(); - assetsDetection.configure({ providerType: 'mainnet' }); + assetsDetection.configure({ networkType: MAINNET }); const detectTokens = sandbox.stub(assetsDetection, 'detectTokens').returns(null); const detectCollectibles = sandbox.stub(assetsDetection, 'detectCollectibles').returns(null); clock.tick(180001); @@ -71,7 +72,7 @@ describe('AssetsDetectionController', () => { clock.tick(180001); expect(detectTokens.called).toBe(false); expect(detectCollectibles.called).toBe(false); - assetsDetection.configure({ providerType: 'mainnet' }); + assetsDetection.configure({ networkType: MAINNET }); clock.tick(180001); expect(detectTokens.called).toBe(true); expect(detectCollectibles.called).toBe(true); @@ -79,7 +80,7 @@ describe('AssetsDetectionController', () => { it('should call detect tokens correctly', () => { const clock = sandbox.useFakeTimers(); - assetsDetection.configure({ providerType: 'mainnet' }); + assetsDetection.configure({ networkType: MAINNET }); const detectTokenOwnership = sandbox.stub(assetsDetection, 'detectTokenOwnership').returns(null); const detectCollectibles = sandbox.stub(assetsDetection, 'detectCollectibles').returns(null); clock.tick(180001); @@ -89,7 +90,7 @@ describe('AssetsDetectionController', () => { it('should call detect collectibles correctly', () => { const clock = sandbox.useFakeTimers(); - assetsDetection.configure({ providerType: 'mainnet' }); + assetsDetection.configure({ networkType: MAINNET }); const detectTokens = sandbox.stub(assetsDetection, 'detectTokens').returns(null); const detectCollectibleOwnership = sandbox.stub(assetsDetection, 'detectCollectibleOwnership').returns(null); clock.tick(180001); @@ -98,7 +99,7 @@ describe('AssetsDetectionController', () => { }); it('should detect tokens correctly', async () => { - assetsDetection.configure({ providerType: 'mainnet' }); + assetsDetection.configure({ networkType: MAINNET }); sandbox .stub(assetsContract, 'getBalanceOf') .returns(new BN(0)) @@ -115,15 +116,15 @@ describe('AssetsDetectionController', () => { }); it('should detect enumerable collectibles correctly', async () => { - getOnce('https://api.godsunchained.com/card/0', () => ({ - body: JSON.stringify({ image: 'https://api.godsunchained.com/v0/image/380', name: 'First Pheonix' }) - })); assetsDetection.configure({ - providerType: 'mainnet', + networkType: MAINNET, selectedAddress: '0x06012c8cf97BEaD5deAe237070F9587f8E7A266d' }); assetsContract.configure({ provider: MAINNET_PROVIDER }); - sandbox.stub(assetsContract, 'getBalanceOf').returns(new BN(1)); + getOnce('https://api.godsunchained.com/card/0', () => ({ + body: JSON.stringify({ image: 'https://api.godsunchained.com/v0/image/380', name: 'First Pheonix' }) + })); + sandbox.stub(assetsContract, 'getBalanceOf').returns(new Promise((resolve) => resolve(new BN(2)))); sandbox.stub(assetsContract, 'getCollectibleTokenId').returns(new Promise((resolve) => resolve(0))); await assetsDetection.detectCollectibleOwnership(GODSADDRESS); expect(assets.state.collectibles).toEqual([ @@ -151,7 +152,7 @@ describe('AssetsDetectionController', () => { }) })); assetsDetection.configure({ - providerType: 'mainnet', + networkType: MAINNET, selectedAddress: '0xb161330dc0d6a9e1cb441b3f2593ba689136b4e4' }); assetsContract.configure({ provider: MAINNET_PROVIDER }); @@ -169,7 +170,7 @@ describe('AssetsDetectionController', () => { it('should not detect asset ownership when no balance of', async () => { assetsDetection.configure({ - providerType: 'mainnet', + networkType: MAINNET, selectedAddress: '0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C' }); assetsContract.configure({ provider: MAINNET_PROVIDER }); @@ -183,15 +184,15 @@ describe('AssetsDetectionController', () => { expect(addToken.called).toBe(false); }); - it('should not detect asset ownership when address in contract metadata', async () => { - assetsDetection.configure({ providerType: 'mainnet' }); + it('should not detect asset ownership when address not in contract metadata', async () => { + assetsDetection.configure({ networkType: MAINNET }); assetsContract.configure({ provider: MAINNET_PROVIDER }); sandbox.stub(assetsContract, 'getBalanceOf').returns(new BN(1)); const getEnumerableCollectiblesIds = sandbox .stub(assetsDetection, 'getEnumerableCollectiblesIds' as any) .returns(false); const getApiCollectiblesIds = sandbox.stub(assetsDetection, 'getApiCollectiblesIds' as any).returns(false); - await assetsDetection.detectCollectibleOwnership('0xfoo'); + await assetsDetection.detectCollectibleOwnership('0xb1690C08E213a35Ed9bAb7B318DE14420FB57d8C'); expect(getEnumerableCollectiblesIds.called).toBe(false); expect(getApiCollectiblesIds.called).toBe(false); }); diff --git a/src/AssetsDetectionController.ts b/src/AssetsDetectionController.ts index 3eea0904a71..2a8cde91561 100644 --- a/src/AssetsDetectionController.ts +++ b/src/AssetsDetectionController.ts @@ -6,6 +6,7 @@ import PreferencesController from './PreferencesController'; import AssetsContractController from './AssetsContractController'; import { safelyExecute } from './util'; import { Token } from './TokenRatesController'; +import { NetworkType } from './NetworkController'; const contractMap = require('eth-contract-metadata'); const DEFAULT_INTERVAL = 180000; @@ -28,13 +29,13 @@ export interface CollectibleEntry { * Assets controller configuration * * @property interval - Polling interval used to fetch new token rates - * @property providerType - Provider type network ID as per net_version + * @property networkType - Network type ID as per net_version * @property selectedAddress - Vault selected address * @property tokens - List of tokens associated with the active vault */ export interface AssetsDetectionConfig extends BaseConfig { interval: number; - providerType: string; + networkType: NetworkType; selectedAddress: string; tokens: Token[]; } @@ -136,7 +137,7 @@ export class AssetsDetectionController extends BaseController await this.detectTokenOwnership(address)); } } } @@ -230,7 +231,7 @@ export class AssetsDetectionController extends BaseController await this.detectCollectibleOwnership(address)); } } } @@ -255,7 +256,7 @@ export class AssetsDetectionController extends BaseController { - this.configure({ providerType: provider.type }); + this.configure({ networkType: provider.type }); }); } } diff --git a/src/TokenBalancesController.test.ts b/src/TokenBalancesController.test.ts index 2d9bf1e2409..6357252b38f 100644 --- a/src/TokenBalancesController.test.ts +++ b/src/TokenBalancesController.test.ts @@ -97,7 +97,7 @@ describe('TokenBalancesController', () => { /* tslint:disable-next-line:no-unused-expression */ new ComposableController([assets, assetsContract, network, preferences, tokenBalances]); const updateBalances = sandbox.stub(tokenBalances, 'updateBalances'); - assets.addToken('0xfoO', 'FOO', 18); + await assets.addToken('0xfoO', 'FOO', 18); const tokens = tokenBalances.context.AssetsController.state.tokens; const found = tokens.filter((token: Token) => token.address === '0xfoO'); expect(found.length > 0).toBe(true); diff --git a/src/TokenRatesController.test.ts b/src/TokenRatesController.test.ts index a85df0f039e..06c3065db30 100644 --- a/src/TokenRatesController.test.ts +++ b/src/TokenRatesController.test.ts @@ -79,7 +79,7 @@ describe('TokenRatesController', () => { const preferences = new PreferencesController(); /* tslint:disable-next-line:no-unused-expression */ new ComposableController([controller, assets, assetsContract, network, preferences]); - assets.addToken('0xfoO', 'FOO', 18); + await assets.addToken('0xfoO', 'FOO', 18); const tokens = controller.context.AssetsController.state.tokens; const found = tokens.filter((token: Token) => token.address === '0xfoO'); expect(found.length > 0).toBe(true);