|
| 1 | +///////////////////////////////////////////////////////////////////////////////// |
| 2 | +// Copyright 2019 StarkWare Industries Ltd. // |
| 3 | +// // |
| 4 | +// Licensed under the Apache License, Version 2.0 (the "License"). // |
| 5 | +// You may not use this file except in compliance with the License. // |
| 6 | +// You may obtain a copy of the License at // |
| 7 | +// // |
| 8 | +// https://www.starkware.co/open-source-license/ // |
| 9 | +// // |
| 10 | +// Unless required by applicable law or agreed to in writing, // |
| 11 | +// software distributed under the License is distributed on an "AS IS" BASIS, // |
| 12 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // |
| 13 | +// See the License for the specific language governing permissions // |
| 14 | +// and limitations under the License. // |
| 15 | +///////////////////////////////////////////////////////////////////////////////// |
| 16 | + |
| 17 | +const BN = require('bn.js'); |
| 18 | +const encUtils = require('enc-utils'); |
| 19 | +const sha3 = require('js-sha3'); |
| 20 | +const assert = require('assert'); |
| 21 | + |
| 22 | + |
| 23 | +// Generate BN of 1. |
| 24 | +const oneBn = new BN('1', 16); |
| 25 | + |
| 26 | +// This number is used to shift the packed encoded asset information by 256 bits. |
| 27 | +const shiftBN = new BN('10000000000000000000000000000000000000000000000000000000000000000', 16); |
| 28 | + |
| 29 | +// Used to mask the 251 least signifcant bits given by Keccack256 to produce the final asset ID. |
| 30 | +const mask = new BN('3ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16); |
| 31 | + |
| 32 | + |
| 33 | +/* |
| 34 | + Computes the hash representing the asset ID for a given asset. |
| 35 | + asset is a dictionary containing the type and data of the asset to parse. the asset type is |
| 36 | + represented by a string describing the associated asset while the data is a dictionary |
| 37 | + containing further infomartion to distinguish between assets of a given type (such as the |
| 38 | + address of the smart contract of an ERC20 asset). |
| 39 | + The function returns the computed asset ID as a hex-string. |
| 40 | +
|
| 41 | + For example: |
| 42 | +
|
| 43 | + assetDict = { |
| 44 | + type: 'ERC20', |
| 45 | + data: { quantum: '10000', tokenAddress: '0xdAC17F958D2ee523a2206206994597C13D831ec7' } |
| 46 | + } |
| 47 | +
|
| 48 | + Will produce an the following asset ID: |
| 49 | +
|
| 50 | + '0x352386d5b7c781d47ecd404765307d74edc4d43b0490b8e03c71ac7a7429653'. |
| 51 | +*/ |
| 52 | +function getAssetType(assetDict) { |
| 53 | + const assetSelector = getAssetSelector(assetDict.type); |
| 54 | + |
| 55 | + // Expected length is maintained to fix the length of the resulting asset info string in case of |
| 56 | + // leading zeroes (which might be omitted by the BN object). |
| 57 | + let expectedLen = encUtils.removeHexPrefix(assetSelector).length; |
| 58 | + |
| 59 | + // The asset info hex string is a packed message containing the hexadecimal representation of |
| 60 | + // the asset data. |
| 61 | + let assetInfo = new BN(encUtils.removeHexPrefix(assetSelector), 16); |
| 62 | + |
| 63 | + if (assetDict.data.tokenAddress !== undefined) { |
| 64 | + // In the case there is a valid tokenAddress in the data, we append that to the asset info |
| 65 | + // (before the quantum). |
| 66 | + const tokenAddress = new BN(encUtils.removeHexPrefix(assetDict.data.tokenAddress), 16); |
| 67 | + assetInfo = assetInfo.mul(shiftBN); |
| 68 | + expectedLen += 64; |
| 69 | + assetInfo = assetInfo.add(tokenAddress); |
| 70 | + } |
| 71 | + |
| 72 | + // Default quantum is 1 (for assets which don't specify quantum explicitly). |
| 73 | + const quantInfo = assetDict.data.quantum; |
| 74 | + const quantum = (quantInfo === undefined) ? oneBn : new BN(quantInfo, 10); |
| 75 | + assetInfo = assetInfo.mul(shiftBN); |
| 76 | + expectedLen += 64; |
| 77 | + assetInfo = assetInfo.add(quantum); |
| 78 | + |
| 79 | + let assetType = sha3.keccak_256( |
| 80 | + encUtils.hexToBuffer(addLeadingZeroes(assetInfo.toJSON(), expectedLen)) |
| 81 | + ); |
| 82 | + assetType = new BN(assetType, 16); |
| 83 | + assetType = assetType.and(mask); |
| 84 | + |
| 85 | + return '0x' + assetType.toJSON(); |
| 86 | +} |
| 87 | + |
| 88 | +function getAssetId(assetDict) { |
| 89 | + const assetType = new BN(encUtils.removeHexPrefix(getAssetType(assetDict)), 16); |
| 90 | + // For ETH and ERC20, the asset ID is simply the asset type. |
| 91 | + let assetId = assetType; |
| 92 | + if (assetDict.type === 'ERC721') { |
| 93 | + // ERC721 assets require a slightly different construction for asset info. |
| 94 | + let assetInfo = new BN(encUtils.utf8ToBuffer('NFT:'), 16); |
| 95 | + assetInfo = assetInfo.mul(shiftBN); |
| 96 | + assetInfo = assetInfo.add(assetType); |
| 97 | + assetInfo = assetInfo.mul(shiftBN); |
| 98 | + assetInfo = assetInfo.add(new BN(parseInt(assetDict.data.tokenId), 16)); |
| 99 | + const expectedLen = 136; |
| 100 | + assetId = sha3.keccak_256( |
| 101 | + encUtils.hexToBuffer(addLeadingZeroes(assetInfo.toJSON(), expectedLen)) |
| 102 | + ); |
| 103 | + assetId = new BN(assetId, 16); |
| 104 | + assetId = assetId.and(mask); |
| 105 | + } |
| 106 | + |
| 107 | + return '0x' + assetId.toJSON(); |
| 108 | +} |
| 109 | + |
| 110 | +/* |
| 111 | + Computes the given asset's unique selector based on its type. |
| 112 | +*/ |
| 113 | +function getAssetSelector(assetDictType) { |
| 114 | + let seed = ''; |
| 115 | + switch (assetDictType.toUpperCase()) { |
| 116 | + case 'ETH': |
| 117 | + seed = 'ETH()'; |
| 118 | + break; |
| 119 | + case 'ERC20': |
| 120 | + seed = 'ERC20Token(address)'; |
| 121 | + break; |
| 122 | + case 'ERC721': |
| 123 | + seed = 'ERC721Token(address,uint256)'; |
| 124 | + break; |
| 125 | + default: |
| 126 | + throw new Error(`Unknown token type: ${assetDictType}`); |
| 127 | + } |
| 128 | + return encUtils.sanitizeHex(sha3.keccak_256(seed).slice(0, 8)); |
| 129 | +} |
| 130 | + |
| 131 | +/* |
| 132 | + Adds leading zeroes to the input hex-string to complement the expected length. |
| 133 | +*/ |
| 134 | +function addLeadingZeroes(hexStr, expectedLen) { |
| 135 | + let res = hexStr; |
| 136 | + assert(res.length <= expectedLen); |
| 137 | + while (res.length < expectedLen) { |
| 138 | + res = '0' + res; |
| 139 | + } |
| 140 | + return res; |
| 141 | +} |
| 142 | + |
| 143 | +module.exports = { |
| 144 | + getAssetType, |
| 145 | + getAssetId // Function. |
| 146 | +}; |
0 commit comments