UNPKG

8.31 kBPlain TextView Raw
1import {
2 AssetJSON,
3 BinaryWriter,
4 common,
5 crypto,
6 ECPoint,
7 IOHelper,
8 JSONHelper,
9 UInt160,
10 UInt256,
11 UInt256Hex,
12} from '@neo-one/client-common-esnext-esm';
13import { BaseState } from '@neo-one/client-full-common-esnext-esm';
14import { makeErrorWithCode } from '@neo-one/utils-esnext-esm';
15import BN from 'bn.js';
16import { assertAssetType, AssetType, toJSONAssetType } from './AssetType';
17import { Equals, EquatableKey } from './Equatable';
18import {
19 createSerializeWire,
20 DeserializeWireBaseOptions,
21 DeserializeWireOptions,
22 SerializableJSON,
23 SerializableWire,
24 SerializeJSONContext,
25 SerializeWire,
26} from './Serializable';
27import { BinaryReader, utils } from './utils';
28
29export const InvalidAssetError = makeErrorWithCode('INVALID_ASSET', (message: string) => message);
30
31export interface AssetKey {
32 readonly hash: UInt256;
33}
34export interface AssetAdd {
35 readonly version?: number;
36 readonly hash: UInt256;
37 readonly type: AssetType;
38 readonly name: string;
39 readonly amount: BN;
40 readonly available?: BN;
41 readonly precision: number;
42 readonly feeMode?: number;
43 readonly fee?: BN;
44 readonly feeAddress?: UInt160;
45 readonly owner: ECPoint;
46 readonly admin: UInt160;
47 readonly issuer: UInt160;
48 readonly expiration: number;
49 readonly isFrozen?: boolean;
50}
51
52export interface AssetUpdate {
53 readonly available?: BN;
54 readonly expiration?: number;
55 readonly isFrozen?: boolean;
56}
57
58const NAME_MAX_LENGTH = 1024;
59const PRECISION_MAX = 8;
60
61export class Asset extends BaseState implements SerializableWire<Asset>, SerializableJSON<AssetJSON>, EquatableKey {
62 public static deserializeWireBase({ reader }: DeserializeWireBaseOptions): Asset {
63 const version = reader.readUInt8();
64 const hash = reader.readUInt256();
65 const type = assertAssetType(reader.readUInt8());
66 const name = reader.readVarString();
67 const amount = reader.readFixed8();
68 const available = reader.readFixed8();
69 const precision = reader.readUInt8();
70 reader.readUInt8(); // FeeMode
71 const fee = reader.readFixed8();
72 const feeAddress = reader.readUInt160();
73 const owner = reader.readECPoint();
74 const admin = reader.readUInt160();
75 const issuer = reader.readUInt160();
76 const expiration = reader.readUInt32LE();
77 const isFrozen = reader.readBoolean();
78
79 return new Asset({
80 version,
81 hash,
82 type,
83 name,
84 amount,
85 available,
86 precision,
87 fee,
88 feeAddress,
89 owner,
90 admin,
91 issuer,
92 expiration,
93 isFrozen,
94 });
95 }
96
97 public static deserializeWire(options: DeserializeWireOptions): Asset {
98 return this.deserializeWireBase({
99 context: options.context,
100 reader: new BinaryReader(options.buffer),
101 });
102 }
103
104 public readonly hash: UInt256;
105 public readonly hashHex: UInt256Hex;
106 public readonly type: AssetType;
107 public readonly name: string;
108 public readonly amount: BN;
109 public readonly available: BN;
110 public readonly precision: number;
111 public readonly feeMode: number;
112 public readonly fee: BN;
113 public readonly feeAddress: UInt160;
114 public readonly owner: ECPoint;
115 public readonly admin: UInt160;
116 public readonly issuer: UInt160;
117 public readonly expiration: number;
118 public readonly isFrozen: boolean;
119 public readonly equals: Equals = utils.equals(Asset, this, (other) => common.uInt256Equal(this.hash, other.hash));
120 public readonly toKeyString = utils.toKeyString(Asset, () => this.hashHex);
121 public readonly serializeWire: SerializeWire = createSerializeWire(this.serializeWireBase.bind(this));
122 private readonly sizeInternal: () => number;
123
124 public constructor({
125 version,
126 hash,
127 type,
128 name,
129 amount,
130 available = utils.ZERO,
131 precision,
132 feeMode = 0,
133 fee = utils.ZERO,
134 feeAddress = common.ZERO_UINT160,
135 owner,
136 admin,
137 issuer,
138 expiration,
139 isFrozen = false,
140 }: AssetAdd) {
141 super({ version });
142 // tslint:disable-next-line
143 verifyAsset({ name, type, amount, precision });
144 this.hash = hash;
145 this.hashHex = common.uInt256ToHex(hash);
146 this.type = type;
147 this.name = name;
148 this.amount = amount;
149 this.available = available;
150 this.precision = precision;
151 this.feeMode = feeMode;
152 this.fee = fee;
153 this.feeAddress = feeAddress;
154 this.owner = owner;
155 this.admin = admin;
156 this.issuer = issuer;
157 this.expiration = expiration;
158 this.isFrozen = isFrozen;
159 this.sizeInternal = utils.lazy(
160 () =>
161 IOHelper.sizeOfUInt8 +
162 IOHelper.sizeOfUInt256 +
163 IOHelper.sizeOfUInt8 +
164 IOHelper.sizeOfVarString(this.name) +
165 IOHelper.sizeOfFixed8 +
166 IOHelper.sizeOfFixed8 +
167 IOHelper.sizeOfUInt8 +
168 IOHelper.sizeOfUInt8 +
169 IOHelper.sizeOfFixed8 +
170 IOHelper.sizeOfUInt160 +
171 IOHelper.sizeOfECPoint(this.owner) +
172 IOHelper.sizeOfUInt160 +
173 IOHelper.sizeOfUInt160 +
174 IOHelper.sizeOfUInt32LE +
175 IOHelper.sizeOfBoolean,
176 );
177 }
178 public get size(): number {
179 return this.sizeInternal();
180 }
181
182 public update({
183 available = this.available,
184 expiration = this.expiration,
185 isFrozen = this.isFrozen,
186 }: AssetUpdate): Asset {
187 return new Asset({
188 hash: this.hash,
189 type: this.type,
190 name: this.name,
191 amount: this.amount,
192 precision: this.precision,
193 fee: this.fee,
194 feeAddress: this.feeAddress,
195 owner: this.owner,
196 admin: this.admin,
197 issuer: this.issuer,
198 available,
199 expiration,
200 isFrozen,
201 });
202 }
203
204 public serializeWireBase(writer: BinaryWriter): void {
205 writer.writeUInt8(this.version);
206 writer.writeUInt256(this.hash);
207 writer.writeUInt8(this.type);
208 writer.writeVarString(this.name);
209 writer.writeFixed8(this.amount);
210 writer.writeFixed8(this.available);
211 writer.writeUInt8(this.precision);
212 writer.writeUInt8(this.feeMode);
213 writer.writeFixed8(this.fee);
214 writer.writeUInt160(this.feeAddress);
215 writer.writeECPoint(this.owner);
216 writer.writeUInt160(this.admin);
217 writer.writeUInt160(this.issuer);
218 writer.writeUInt32LE(this.expiration);
219 writer.writeBoolean(this.isFrozen);
220 }
221
222 public serializeJSON(context: SerializeJSONContext): AssetJSON {
223 let name = this.name;
224 try {
225 name = JSON.parse(name);
226 } catch {
227 // Ignore errors
228 }
229
230 return {
231 version: this.version,
232 id: JSONHelper.writeUInt256(this.hash),
233 type: toJSONAssetType(this.type),
234 name,
235 amount: JSONHelper.writeFixed8(this.amount),
236 available: JSONHelper.writeFixed8(this.available),
237 precision: this.precision,
238 owner: JSONHelper.writeECPoint(this.owner),
239 admin: crypto.scriptHashToAddress({
240 addressVersion: context.addressVersion,
241 scriptHash: this.admin,
242 }),
243
244 issuer: crypto.scriptHashToAddress({
245 addressVersion: context.addressVersion,
246 scriptHash: this.issuer,
247 }),
248
249 expiration: this.expiration,
250 frozen: this.isFrozen,
251 };
252 }
253}
254
255export const verifyAsset = ({
256 name,
257 type,
258 amount,
259 precision,
260}: {
261 readonly name: AssetAdd['name'];
262 readonly type: AssetAdd['type'];
263 readonly amount: AssetAdd['amount'];
264 readonly precision: AssetAdd['precision'];
265}) => {
266 if (type === AssetType.CreditFlag || type === AssetType.DutyFlag) {
267 throw new InvalidAssetError(`Asset type cannot be CREDIT_FLAG or DUTY_FLAG, received: ${type}`);
268 }
269
270 const nameBuffer = Buffer.from(name, 'utf8');
271 if (nameBuffer.length > NAME_MAX_LENGTH) {
272 throw new InvalidAssetError(`Name too long. Max: ${NAME_MAX_LENGTH}, Received: ${nameBuffer.length}`);
273 }
274
275 if (amount.lte(utils.ZERO) && !amount.eq(common.NEGATIVE_SATOSHI_FIXED8)) {
276 throw new InvalidAssetError(`Amount must be greater than 0. (received ${amount})`);
277 }
278
279 if (type === AssetType.Invoice && !amount.eq(common.NEGATIVE_SATOSHI_FIXED8)) {
280 throw new InvalidAssetError('Invoice assets must have unlimited amount.');
281 }
282
283 if (precision > PRECISION_MAX) {
284 throw new InvalidAssetError(`Max precision is 8. Received: ${precision}`);
285 }
286
287 if (!amount.eq(utils.NEGATIVE_SATOSHI) && !amount.mod(utils.TEN.pow(utils.EIGHT.subn(precision))).eq(utils.ZERO)) {
288 throw new InvalidAssetError('Invalid precision for amount.');
289 }
290};