1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
import { AssetProxyId, ERC20AssetData, ERC721AssetData } from '@0xproject/types';
import { BigNumber } from '@0xproject/utils';
import BN = require('bn.js');
import ethUtil = require('ethereumjs-util');
const ERC20_ASSET_DATA_BYTE_LENGTH = 36;
const ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH = 53;
const ASSET_DATA_ADDRESS_OFFSET = 0;
const ERC721_ASSET_DATA_TOKEN_ID_OFFSET = 20;
const ERC721_ASSET_DATA_RECEIVER_DATA_LENGTH_OFFSET = 52;
const ERC721_ASSET_DATA_RECEIVER_DATA_OFFSET = 84;
// TODO: Push upstream to DefinitelyTyped
interface EthAbi {
simpleEncode(signature: string, ...args: any[]): Buffer;
rawDecode(signature: string[], data: Buffer): any[];
}
const ethAbi = require('ethereumjs-abi') as EthAbi;
export const assetProxyUtils = {
encodeAssetProxyId(assetProxyId: AssetProxyId): Buffer {
return ethUtil.toBuffer(assetProxyId);
},
decodeAssetProxyId(encodedAssetProxyId: Buffer): AssetProxyId {
const string = ethUtil.bufferToHex(encodedAssetProxyId);
if (string === AssetProxyId.ERC20) {
return AssetProxyId.ERC20;
}
if (string === AssetProxyId.ERC721) {
return AssetProxyId.ERC721;
}
throw new Error(`Invalid ProxyId: ${string}`);
},
encodeAddress(address: string): Buffer {
if (!ethUtil.isValidAddress(address)) {
throw new Error(`Invalid Address: ${address}`);
}
const encodedAddress = ethUtil.toBuffer(address);
const padded = ethUtil.setLengthLeft(encodedAddress, 32);
return padded;
},
decodeAddress(encodedAddress: Buffer): string {
const unpadded = ethUtil.setLengthLeft(encodedAddress, 20);
const address = ethUtil.bufferToHex(unpadded);
if (!ethUtil.isValidAddress(address)) {
throw new Error(`Invalid Address: ${address}`);
}
return address;
},
encodeUint256(value: BigNumber): Buffer {
const base = 10;
const formattedValue = new BN(value.toString(base));
const encodedValue = ethUtil.toBuffer(formattedValue);
// tslint:disable-next-line:custom-no-magic-numbers
const paddedValue = ethUtil.setLengthLeft(encodedValue, 32);
return paddedValue;
},
decodeUint256(encodedValue: Buffer): BigNumber {
const formattedValue = ethUtil.bufferToHex(encodedValue);
const value = new BigNumber(formattedValue, 16);
return value;
},
encodeERC20AssetData(tokenAddress: string): string {
return ethUtil.bufferToHex(ethAbi.simpleEncode(
'ERC20Token(address)',
tokenAddress,
));
},
decodeERC20AssetData(assetData: string): ERC20AssetData {
const data = ethUtil.toBuffer(assetData);
if (data.byteLength < ERC20_ASSET_DATA_BYTE_LENGTH) {
throw new Error(
`Could not decode ERC20 Proxy Data. Expected length of encoded data to be at least ${ERC20_ASSET_DATA_BYTE_LENGTH}. Got ${data.byteLength}`,
);
}
const assetProxyId = ethUtil.bufferToHex(data.slice(0, 4));
if (assetProxyId !== AssetProxyId.ERC20) {
throw new Error(
`Could not decode ERC20 Proxy Data. Expected Asset Proxy Id to be ERC20 (${AssetProxyId.ERC20}), but got ${assetProxyId}`,
);
}
const [tokenAddress] = ethAbi.rawDecode(['address'], data.slice(4));
return {
assetProxyId,
tokenAddress: ethUtil.addHexPrefix(tokenAddress),
};
},
encodeERC721AssetData(tokenAddress: string, tokenId: BigNumber, receiverData?: string): string {
// TODO: Pass `tokendId` as a BigNumber.
return ethUtil.bufferToHex(ethAbi.simpleEncode(
'ERC721Token(address,uint256,bytes)',
tokenAddress,
'0x' + tokenId.toString(16),
ethUtil.toBuffer(receiverData || '0x'),
));
},
decodeERC721AssetData(assetData: string): ERC721AssetData {
const data = ethUtil.toBuffer(assetData);
if (data.byteLength < ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH) {
throw new Error(
`Could not decode ERC721 Asset Data. Expected length of encoded data to be at least ${ERC721_ASSET_DATA_MINIMUM_BYTE_LENGTH}. Got ${data.byteLength}`,
);
}
const assetProxyId = ethUtil.bufferToHex(data.slice(0, 4));
if (assetProxyId !== AssetProxyId.ERC721) {
throw new Error(
`Could not decode ERC721 Asset Data. Expected Asset Proxy Id to be ERC721 (${AssetProxyId.ERC721}), but got ${assetProxyId}`,
);
}
const [tokenAddress, tokenId, receiverData] = ethAbi.rawDecode(
['address', 'uint256', 'bytes'],
data.slice(4),
);
return {
assetProxyId,
tokenAddress: ethUtil.addHexPrefix(tokenAddress),
tokenId: new BigNumber(tokenId.toString()),
receiverData: ethUtil.bufferToHex(receiverData),
};
},
decodeAssetDataId(assetData: string): AssetProxyId {
const encodedAssetData = ethUtil.toBuffer(assetData);
if (encodedAssetData.byteLength < 4) {
throw new Error(
`Could not decode Proxy Data. Expected length of encoded data to be at least 4. Got ${encodedAssetData.byteLength}`,
);
}
const encodedAssetProxyId = encodedAssetData.slice(0, 4);
const assetProxyId = assetProxyUtils.decodeAssetProxyId(encodedAssetProxyId);
return assetProxyId;
},
decodeAssetData(assetData: string): ERC20AssetData | ERC721AssetData {
const assetProxyId = assetProxyUtils.decodeAssetDataId(assetData);
switch (assetProxyId) {
case AssetProxyId.ERC20:
const erc20AssetData = assetProxyUtils.decodeERC20AssetData(assetData);
return erc20AssetData;
case AssetProxyId.ERC721:
const erc721AssetData = assetProxyUtils.decodeERC721AssetData(assetData);
return erc721AssetData;
default:
throw new Error(`Unrecognized asset proxy id: ${assetProxyId}`);
}
},
};
|