@btc-vision/btc-runtime 1.9.9 → 1.9.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/runtime/buffer/BytesReader.ts +11 -3
- package/runtime/buffer/BytesWriter.ts +32 -7
- package/runtime/constants/Exports.ts +5 -5
- package/runtime/contracts/OP20.ts +46 -12
- package/runtime/contracts/OP721.ts +60 -85
- package/runtime/contracts/interfaces/IOP721.ts +3 -2
- package/runtime/env/classes/UTXO.ts +5 -0
- package/runtime/env/decoders/TransactionDecoder.ts +8 -1
- package/runtime/env/enums/TransactionFlags.ts +1 -0
package/package.json
CHANGED
|
@@ -203,8 +203,7 @@ export class BytesReader {
|
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
/**
|
|
206
|
-
* [u32 length][raw bytes]. By default big-endian for the length
|
|
207
|
-
* to match AS BytesWriter's `writeBytesWithLength`.
|
|
206
|
+
* [u32 length][raw bytes]. By default big-endian for the length
|
|
208
207
|
*/
|
|
209
208
|
public readBytesWithLength(be: boolean = true): Uint8Array {
|
|
210
209
|
const length = this.readU32(be);
|
|
@@ -248,8 +247,17 @@ export class BytesReader {
|
|
|
248
247
|
|
|
249
248
|
// ------------------- Arrays ------------------- //
|
|
250
249
|
|
|
250
|
+
public readArrayOfBuffer(be: boolean = true): Uint8Array[] {
|
|
251
|
+
const length = this.readU16(be);
|
|
252
|
+
const result: Uint8Array[] = new Array<Uint8Array>(length);
|
|
253
|
+
for (let i: u32 = 0; i < length; i++) {
|
|
254
|
+
result[i] = this.readBytesWithLength();
|
|
255
|
+
}
|
|
256
|
+
return result;
|
|
257
|
+
}
|
|
258
|
+
|
|
251
259
|
public readU256Array(be: boolean = true): u256[] {
|
|
252
|
-
const length = this.readU16();
|
|
260
|
+
const length = this.readU16(be);
|
|
253
261
|
const result = new Array<u256>(length);
|
|
254
262
|
for (let i: u32 = 0; i < length; i++) {
|
|
255
263
|
result[i] = this.readU256(be);
|
|
@@ -19,6 +19,8 @@ import {
|
|
|
19
19
|
} from '../utils';
|
|
20
20
|
import { BytesReader } from './BytesReader';
|
|
21
21
|
|
|
22
|
+
const arrayTooLargeError: string = 'Array is too large';
|
|
23
|
+
|
|
22
24
|
@final
|
|
23
25
|
export class BytesWriter {
|
|
24
26
|
private currentOffset: u32 = 0;
|
|
@@ -30,6 +32,17 @@ export class BytesWriter {
|
|
|
30
32
|
this.buffer = new DataView(typedArray.buffer);
|
|
31
33
|
}
|
|
32
34
|
|
|
35
|
+
public static estimateArrayOfBufferLength(values: Uint8Array[]): u32 {
|
|
36
|
+
if (values.length > 65535) throw new Revert(arrayTooLargeError);
|
|
37
|
+
let totalLength: u32 = U16_BYTE_LENGTH;
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < values.length; i++) {
|
|
40
|
+
totalLength += U32_BYTE_LENGTH + u32(values[i].length); // each entry has a u32 length prefix
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return totalLength;
|
|
44
|
+
}
|
|
45
|
+
|
|
33
46
|
public bufferLength(): u32 {
|
|
34
47
|
return this.buffer.byteLength;
|
|
35
48
|
}
|
|
@@ -197,8 +210,20 @@ export class BytesWriter {
|
|
|
197
210
|
|
|
198
211
|
// ------------------ Array Writers ------------------ //
|
|
199
212
|
|
|
213
|
+
public writeArrayOfBuffer(values: Uint8Array[], be: boolean = true): void {
|
|
214
|
+
const totalLength = BytesWriter.estimateArrayOfBufferLength(values);
|
|
215
|
+
|
|
216
|
+
this.allocSafe(totalLength);
|
|
217
|
+
this.writeU16(u16(values.length), be);
|
|
218
|
+
|
|
219
|
+
for (let i = 0; i < values.length; i++) {
|
|
220
|
+
this.writeU32(u32(values[i].length), be);
|
|
221
|
+
this.writeBytes(values[i]);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
200
225
|
public writeU8Array(value: u8[], be: boolean = true): void {
|
|
201
|
-
if (value.length > 65535) throw new Revert(
|
|
226
|
+
if (value.length > 65535) throw new Revert(arrayTooLargeError);
|
|
202
227
|
this.allocSafe(U16_BYTE_LENGTH + value.length);
|
|
203
228
|
this.writeU16(u16(value.length), be);
|
|
204
229
|
|
|
@@ -208,7 +233,7 @@ export class BytesWriter {
|
|
|
208
233
|
}
|
|
209
234
|
|
|
210
235
|
public writeU16Array(value: u16[], be: boolean = true): void {
|
|
211
|
-
if (value.length > 65535) throw new Revert(
|
|
236
|
+
if (value.length > 65535) throw new Revert(arrayTooLargeError);
|
|
212
237
|
this.allocSafe(U16_BYTE_LENGTH + value.length * U16_BYTE_LENGTH);
|
|
213
238
|
this.writeU16(u16(value.length), be);
|
|
214
239
|
|
|
@@ -218,7 +243,7 @@ export class BytesWriter {
|
|
|
218
243
|
}
|
|
219
244
|
|
|
220
245
|
public writeU32Array(value: u32[], be: boolean = true): void {
|
|
221
|
-
if (value.length > 65535) throw new Revert(
|
|
246
|
+
if (value.length > 65535) throw new Revert(arrayTooLargeError);
|
|
222
247
|
this.allocSafe(U16_BYTE_LENGTH + value.length * U32_BYTE_LENGTH);
|
|
223
248
|
this.writeU16(u16(value.length), be);
|
|
224
249
|
|
|
@@ -228,7 +253,7 @@ export class BytesWriter {
|
|
|
228
253
|
}
|
|
229
254
|
|
|
230
255
|
public writeU64Array(value: u64[], be: boolean = true): void {
|
|
231
|
-
if (value.length > 65535) throw new Revert(
|
|
256
|
+
if (value.length > 65535) throw new Revert(arrayTooLargeError);
|
|
232
257
|
this.allocSafe(U16_BYTE_LENGTH + value.length * U64_BYTE_LENGTH);
|
|
233
258
|
this.writeU16(u16(value.length), be);
|
|
234
259
|
|
|
@@ -238,7 +263,7 @@ export class BytesWriter {
|
|
|
238
263
|
}
|
|
239
264
|
|
|
240
265
|
public writeU128Array(value: u128[], be: boolean = true): void {
|
|
241
|
-
if (value.length > 65535) throw new Revert(
|
|
266
|
+
if (value.length > 65535) throw new Revert(arrayTooLargeError);
|
|
242
267
|
this.allocSafe(U16_BYTE_LENGTH + value.length * U128_BYTE_LENGTH);
|
|
243
268
|
this.writeU16(u16(value.length), be);
|
|
244
269
|
|
|
@@ -248,7 +273,7 @@ export class BytesWriter {
|
|
|
248
273
|
}
|
|
249
274
|
|
|
250
275
|
public writeU256Array(value: u256[], be: boolean = true): void {
|
|
251
|
-
if (value.length > 65535) throw new Revert(
|
|
276
|
+
if (value.length > 65535) throw new Revert(arrayTooLargeError);
|
|
252
277
|
this.allocSafe(U16_BYTE_LENGTH + value.length * U256_BYTE_LENGTH);
|
|
253
278
|
this.writeU16(u16(value.length), be);
|
|
254
279
|
|
|
@@ -258,7 +283,7 @@ export class BytesWriter {
|
|
|
258
283
|
}
|
|
259
284
|
|
|
260
285
|
public writeAddressArray(value: Address[]): void {
|
|
261
|
-
if (value.length > 65535) throw new Revert(
|
|
286
|
+
if (value.length > 65535) throw new Revert(arrayTooLargeError);
|
|
262
287
|
this.writeU16(u16(value.length));
|
|
263
288
|
|
|
264
289
|
for (let i: i32 = 0; i < value.length; i++) {
|
|
@@ -14,7 +14,7 @@ export const ALLOWANCE_DECREASE_TYPE_HASH: u8[] = [
|
|
|
14
14
|
];
|
|
15
15
|
|
|
16
16
|
// onOP721Received(address,address,uint256,bytes)
|
|
17
|
-
export const ON_OP721_RECEIVED_SELECTOR: u32 =
|
|
17
|
+
export const ON_OP721_RECEIVED_SELECTOR: u32 = 0x5349f6de;
|
|
18
18
|
|
|
19
19
|
// onOP1155Received(address,address,uint256,uint256,bytes)
|
|
20
20
|
export const ON_OP1155_RECEIVED_MAGIC: u32 = 0xcedc9fdf;
|
|
@@ -61,10 +61,10 @@ export const OP712_VERSION_HASH: u8[] = [
|
|
|
61
61
|
0x47, 0xad, 0xa4, 0xea, 0xa2, 0x2f, 0x1d, 0x49, 0xc0, 0x1e, 0x52, 0xdd, 0xb7, 0x87, 0x5b, 0x4b,
|
|
62
62
|
];
|
|
63
63
|
|
|
64
|
-
// sha256("
|
|
65
|
-
export const
|
|
66
|
-
|
|
67
|
-
|
|
64
|
+
// sha256("OP721Approval(address owner,address spender,uint256 tokenId,uint256 nonce,uint64 deadline)")
|
|
65
|
+
export const OP721_APPROVAL_TYPE_HASH: u8[] = [
|
|
66
|
+
0xb8, 0x6e, 0x99, 0xda, 0xc0, 0x47, 0x4b, 0x4a, 0x9f, 0xc3, 0x32, 0x3a, 0xd6, 0xed, 0x2f, 0x39,
|
|
67
|
+
0x55, 0xe7, 0xb8, 0x6d, 0xc6, 0x8c, 0x62, 0x42, 0x82, 0x1c, 0xbc, 0xac, 0xa2, 0xd8, 0x79, 0xde
|
|
68
68
|
];
|
|
69
69
|
|
|
70
70
|
// sha256("OP721Transfer(address from,address to,uint256 tokenId,uint256 nonce,uint64 deadline)")
|
|
@@ -214,6 +214,37 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
|
|
|
214
214
|
return w;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
|
+
@method(
|
|
218
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
219
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
220
|
+
)
|
|
221
|
+
@emit('Transferred')
|
|
222
|
+
public transfer(calldata: Calldata): BytesWriter {
|
|
223
|
+
this._transfer(
|
|
224
|
+
Blockchain.tx.sender,
|
|
225
|
+
calldata.readAddress(),
|
|
226
|
+
calldata.readU256(),
|
|
227
|
+
);
|
|
228
|
+
return new BytesWriter(0);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@method(
|
|
232
|
+
{ name: 'from', type: ABIDataTypes.ADDRESS },
|
|
233
|
+
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
234
|
+
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
235
|
+
)
|
|
236
|
+
@emit('Transferred')
|
|
237
|
+
public transferFrom(calldata: Calldata): BytesWriter {
|
|
238
|
+
const from = calldata.readAddress();
|
|
239
|
+
const to = calldata.readAddress();
|
|
240
|
+
const amount = calldata.readU256();
|
|
241
|
+
|
|
242
|
+
this._spendAllowance(from, Blockchain.tx.sender, amount);
|
|
243
|
+
this._transfer(from, to, amount);
|
|
244
|
+
|
|
245
|
+
return new BytesWriter(0);
|
|
246
|
+
}
|
|
247
|
+
|
|
217
248
|
@method(
|
|
218
249
|
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
219
250
|
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
@@ -221,7 +252,7 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
|
|
|
221
252
|
)
|
|
222
253
|
@emit('Transferred')
|
|
223
254
|
public safeTransfer(calldata: Calldata): BytesWriter {
|
|
224
|
-
this.
|
|
255
|
+
this._safeTransfer(
|
|
225
256
|
Blockchain.tx.sender,
|
|
226
257
|
calldata.readAddress(),
|
|
227
258
|
calldata.readU256(),
|
|
@@ -244,7 +275,7 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
|
|
|
244
275
|
const data = calldata.readBytesWithLength();
|
|
245
276
|
|
|
246
277
|
this._spendAllowance(from, Blockchain.tx.sender, amount);
|
|
247
|
-
this.
|
|
278
|
+
this._safeTransfer(from, to, amount, data);
|
|
248
279
|
|
|
249
280
|
return new BytesWriter(0);
|
|
250
281
|
}
|
|
@@ -326,10 +357,9 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
|
|
|
326
357
|
@returns(
|
|
327
358
|
{ name: 'name', type: ABIDataTypes.STRING },
|
|
328
359
|
{ name: 'symbol', type: ABIDataTypes.STRING },
|
|
360
|
+
{ name: 'icon', type: ABIDataTypes.STRING },
|
|
329
361
|
{ name: 'decimals', type: ABIDataTypes.UINT8 },
|
|
330
362
|
{ name: 'totalSupply', type: ABIDataTypes.UINT256 },
|
|
331
|
-
{ name: 'maximumSupply', type: ABIDataTypes.UINT256 },
|
|
332
|
-
{ name: 'icon', type: ABIDataTypes.STRING },
|
|
333
363
|
{ name: 'domainSeparator', type: ABIDataTypes.BYTES32 },
|
|
334
364
|
)
|
|
335
365
|
public metadata(_: Calldata): BytesWriter {
|
|
@@ -354,10 +384,9 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
|
|
|
354
384
|
const w = new BytesWriter(totalSize);
|
|
355
385
|
w.writeStringWithLength(name);
|
|
356
386
|
w.writeStringWithLength(symbol);
|
|
387
|
+
w.writeStringWithLength(icon);
|
|
357
388
|
w.writeU8(this.decimals);
|
|
358
389
|
w.writeU256(this.totalSupply);
|
|
359
|
-
w.writeU256(this.maxSupply);
|
|
360
|
-
w.writeStringWithLength(icon);
|
|
361
390
|
w.writeBytesWithLength(domainSeparator);
|
|
362
391
|
|
|
363
392
|
return w;
|
|
@@ -373,7 +402,7 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
|
|
|
373
402
|
return senderMap.get(spender);
|
|
374
403
|
}
|
|
375
404
|
|
|
376
|
-
protected _transfer(from: Address, to: Address, amount: u256,
|
|
405
|
+
protected _transfer(from: Address, to: Address, amount: u256, emitEvent: boolean = true): void {
|
|
377
406
|
if (from === Address.zero() || from === Address.dead()) {
|
|
378
407
|
throw new Revert('Invalid sender');
|
|
379
408
|
}
|
|
@@ -382,10 +411,6 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
|
|
|
382
411
|
throw new Revert('Invalid receiver');
|
|
383
412
|
}
|
|
384
413
|
|
|
385
|
-
if (amount.isZero()) {
|
|
386
|
-
throw new Revert('Amount must be greater than zero');
|
|
387
|
-
}
|
|
388
|
-
|
|
389
414
|
const balance: u256 = this.balanceOfMap.get(from);
|
|
390
415
|
if (balance < amount) {
|
|
391
416
|
throw new Revert('Insufficient balance');
|
|
@@ -396,13 +421,22 @@ export abstract class OP20 extends ReentrancyGuard implements IOP20 {
|
|
|
396
421
|
const toBal: u256 = this.balanceOfMap.get(to);
|
|
397
422
|
this.balanceOfMap.set(to, SafeMath.add(toBal, amount));
|
|
398
423
|
|
|
399
|
-
|
|
424
|
+
if (emitEvent) {
|
|
425
|
+
this.createTransferredEvent(Blockchain.tx.sender, from, to, amount);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
protected _safeTransfer(from: Address, to: Address, amount: u256, data: Uint8Array): void {
|
|
430
|
+
this._transfer(from, to, amount, false);
|
|
400
431
|
|
|
401
432
|
if (Blockchain.isContract(to)) {
|
|
402
433
|
// In CALLBACK mode, the guard allows depth up to 1
|
|
403
434
|
// In STANDARD mode, the guard blocks all reentrancy
|
|
404
435
|
this._callOnOP20Received(from, to, amount, data);
|
|
405
436
|
}
|
|
437
|
+
|
|
438
|
+
// Fire event at the end if everything succeeded indicating a successful transfer
|
|
439
|
+
this.createTransferredEvent(Blockchain.tx.sender, from, to, amount);
|
|
406
440
|
}
|
|
407
441
|
|
|
408
442
|
protected _spendAllowance(owner: Address, spender: Address, amount: u256): void {
|
|
@@ -24,19 +24,12 @@ import { IOP721 } from './interfaces/IOP721';
|
|
|
24
24
|
import { OP721InitParameters } from './interfaces/OP721InitParameters';
|
|
25
25
|
import { ReentrancyGuard } from './ReentrancyGuard';
|
|
26
26
|
import { StoredMapU256 } from '../storage/maps/StoredMapU256';
|
|
27
|
-
import {
|
|
28
|
-
ApprovedEvent,
|
|
29
|
-
ApprovedForAllEvent,
|
|
30
|
-
MAX_URI_LENGTH,
|
|
31
|
-
TransferredEvent,
|
|
32
|
-
URIEvent,
|
|
33
|
-
} from '../events/predefined';
|
|
27
|
+
import { ApprovedEvent, ApprovedForAllEvent, MAX_URI_LENGTH, TransferredEvent, URIEvent } from '../events/predefined';
|
|
34
28
|
import {
|
|
35
29
|
ON_OP721_RECEIVED_SELECTOR,
|
|
36
30
|
OP712_DOMAIN_TYPE_HASH,
|
|
37
31
|
OP712_VERSION_HASH,
|
|
38
|
-
|
|
39
|
-
OP721_TRANSFER_TYPE_HASH,
|
|
32
|
+
OP721_APPROVAL_TYPE_HASH,
|
|
40
33
|
} from '../constants/Exports';
|
|
41
34
|
|
|
42
35
|
const stringPointer: u16 = Blockchain.nextPointer;
|
|
@@ -52,7 +45,6 @@ const ownerTokensMapPointer: u16 = Blockchain.nextPointer;
|
|
|
52
45
|
const tokenIndexMapPointer: u16 = Blockchain.nextPointer;
|
|
53
46
|
const initializedPointer: u16 = Blockchain.nextPointer;
|
|
54
47
|
const tokenURICounterPointer: u16 = Blockchain.nextPointer;
|
|
55
|
-
const transferNonceMapPointer: u16 = Blockchain.nextPointer;
|
|
56
48
|
const approveNonceMapPointer: u16 = Blockchain.nextPointer;
|
|
57
49
|
|
|
58
50
|
export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
@@ -75,8 +67,6 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
75
67
|
protected readonly balanceOfMap: AddressMemoryMap;
|
|
76
68
|
protected readonly operatorApprovalMap: MapOfMap<u256>;
|
|
77
69
|
|
|
78
|
-
// Separate nonces for different operations
|
|
79
|
-
protected readonly _transferNonceMap: AddressMemoryMap;
|
|
80
70
|
protected readonly _approveNonceMap: AddressMemoryMap;
|
|
81
71
|
|
|
82
72
|
// Token URI storage - stores index to StoredString array
|
|
@@ -112,7 +102,6 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
112
102
|
this.operatorApprovalMap = new MapOfMap<u256>(operatorApprovalMapPointer);
|
|
113
103
|
|
|
114
104
|
// Initialize separate nonce maps
|
|
115
|
-
this._transferNonceMap = new AddressMemoryMap(transferNonceMapPointer);
|
|
116
105
|
this._approveNonceMap = new AddressMemoryMap(approveNonceMapPointer);
|
|
117
106
|
|
|
118
107
|
this.tokenURIIndices = new StoredMapU256(tokenURIMapPointer);
|
|
@@ -308,25 +297,17 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
308
297
|
}
|
|
309
298
|
|
|
310
299
|
@method(
|
|
311
|
-
{ name: 'from', type: ABIDataTypes.ADDRESS },
|
|
312
300
|
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
313
301
|
{ name: 'tokenId', type: ABIDataTypes.UINT256 },
|
|
314
302
|
{ name: 'data', type: ABIDataTypes.BYTES },
|
|
315
303
|
)
|
|
316
304
|
@emit('Transferred')
|
|
317
|
-
public
|
|
318
|
-
const from = calldata.readAddress();
|
|
305
|
+
public safeTransfer(calldata: Calldata): BytesWriter {
|
|
319
306
|
const to = calldata.readAddress();
|
|
320
307
|
const tokenId = calldata.readU256();
|
|
321
308
|
const data = calldata.readBytesWithLength();
|
|
322
309
|
|
|
323
|
-
|
|
324
|
-
this._transfer(from, to, tokenId);
|
|
325
|
-
|
|
326
|
-
// External call happens after all state changes
|
|
327
|
-
if (Blockchain.isContract(to)) {
|
|
328
|
-
this._checkOnOP721Received(from, to, tokenId, data);
|
|
329
|
-
}
|
|
310
|
+
this._transfer(Blockchain.tx.sender, to, tokenId, data);
|
|
330
311
|
|
|
331
312
|
return new BytesWriter(0);
|
|
332
313
|
}
|
|
@@ -335,32 +316,34 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
335
316
|
{ name: 'from', type: ABIDataTypes.ADDRESS },
|
|
336
317
|
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
337
318
|
{ name: 'tokenId', type: ABIDataTypes.UINT256 },
|
|
319
|
+
{ name: 'data', type: ABIDataTypes.BYTES },
|
|
338
320
|
)
|
|
339
321
|
@emit('Transferred')
|
|
340
|
-
public
|
|
322
|
+
public safeTransferFrom(calldata: Calldata): BytesWriter {
|
|
341
323
|
const from = calldata.readAddress();
|
|
342
324
|
const to = calldata.readAddress();
|
|
343
325
|
const tokenId = calldata.readU256();
|
|
326
|
+
const data = calldata.readBytesWithLength();
|
|
344
327
|
|
|
345
|
-
this._transfer(from, to, tokenId);
|
|
328
|
+
this._transfer(from, to, tokenId, data);
|
|
346
329
|
|
|
347
330
|
return new BytesWriter(0);
|
|
348
331
|
}
|
|
349
332
|
|
|
350
333
|
@method(
|
|
351
|
-
{ name: '
|
|
334
|
+
{ name: 'operator', type: ABIDataTypes.ADDRESS },
|
|
352
335
|
{ name: 'tokenId', type: ABIDataTypes.UINT256 },
|
|
353
336
|
)
|
|
354
337
|
@emit('Approved')
|
|
355
338
|
public approve(calldata: Calldata): BytesWriter {
|
|
356
|
-
const
|
|
339
|
+
const operator = calldata.readAddress();
|
|
357
340
|
const tokenId = calldata.readU256();
|
|
358
341
|
|
|
359
342
|
// Validate to address
|
|
360
|
-
if (
|
|
343
|
+
if (operator === Address.zero()) throw new Revert('Cannot approve zero address');
|
|
361
344
|
|
|
362
345
|
const owner = this._ownerOf(tokenId);
|
|
363
|
-
if (
|
|
346
|
+
if (operator === owner) throw new Revert('Approval to current owner');
|
|
364
347
|
|
|
365
348
|
if (
|
|
366
349
|
owner !== Blockchain.tx.sender &&
|
|
@@ -369,13 +352,12 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
369
352
|
throw new Revert('Not authorized to approve');
|
|
370
353
|
}
|
|
371
354
|
|
|
372
|
-
this._approve(
|
|
355
|
+
this._approve(operator, tokenId);
|
|
373
356
|
|
|
374
357
|
return new BytesWriter(0);
|
|
375
358
|
}
|
|
376
359
|
|
|
377
360
|
@method({ name: 'tokenId', type: ABIDataTypes.UINT256 })
|
|
378
|
-
@returns({ name: 'approved', type: ABIDataTypes.ADDRESS })
|
|
379
361
|
public getApproved(calldata: Calldata): BytesWriter {
|
|
380
362
|
const tokenId = calldata.readU256();
|
|
381
363
|
if (!this._exists(tokenId)) throw new Revert('Token does not exist');
|
|
@@ -419,47 +401,50 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
419
401
|
|
|
420
402
|
@method(
|
|
421
403
|
{ name: 'owner', type: ABIDataTypes.ADDRESS },
|
|
422
|
-
{ name: '
|
|
404
|
+
{ name: 'operator', type: ABIDataTypes.ADDRESS },
|
|
423
405
|
{ name: 'tokenId', type: ABIDataTypes.UINT256 },
|
|
424
406
|
{ name: 'deadline', type: ABIDataTypes.UINT64 },
|
|
425
407
|
{ name: 'signature', type: ABIDataTypes.BYTES },
|
|
426
408
|
)
|
|
427
|
-
@emit('
|
|
428
|
-
public
|
|
409
|
+
@emit('Approved')
|
|
410
|
+
public approveBySignature(calldata: Calldata): BytesWriter {
|
|
429
411
|
const owner = calldata.readAddress();
|
|
430
|
-
const
|
|
412
|
+
const operator = calldata.readAddress();
|
|
431
413
|
const tokenId = calldata.readU256();
|
|
432
414
|
const deadline = calldata.readU64();
|
|
433
415
|
const signature = calldata.readBytesWithLength();
|
|
434
416
|
|
|
435
|
-
|
|
436
|
-
this.
|
|
417
|
+
// Verify ownership
|
|
418
|
+
const tokenOwner = this._ownerOf(tokenId);
|
|
419
|
+
if (tokenOwner !== owner) throw new Revert('Not token owner');
|
|
420
|
+
|
|
421
|
+
this._verifyApproveSignature(owner, operator, tokenId, deadline, signature);
|
|
422
|
+
|
|
423
|
+
this._approve(operator, tokenId);
|
|
437
424
|
|
|
438
425
|
return new BytesWriter(0);
|
|
439
426
|
}
|
|
440
427
|
|
|
441
428
|
@method(
|
|
442
429
|
{ name: 'owner', type: ABIDataTypes.ADDRESS },
|
|
443
|
-
{ name: '
|
|
444
|
-
{ name: '
|
|
430
|
+
{ name: 'operator', type: ABIDataTypes.ADDRESS },
|
|
431
|
+
{ name: 'approved', type: ABIDataTypes.BOOL },
|
|
445
432
|
{ name: 'deadline', type: ABIDataTypes.UINT64 },
|
|
446
433
|
{ name: 'signature', type: ABIDataTypes.BYTES },
|
|
447
434
|
)
|
|
448
435
|
@emit('Approved')
|
|
449
|
-
public
|
|
436
|
+
public setApprovalForAllBySignature(calldata: Calldata): BytesWriter {
|
|
450
437
|
const owner = calldata.readAddress();
|
|
451
|
-
const
|
|
452
|
-
const
|
|
438
|
+
const operator = calldata.readAddress();
|
|
439
|
+
const approved = calldata.readBoolean();
|
|
453
440
|
const deadline = calldata.readU64();
|
|
454
441
|
const signature = calldata.readBytesWithLength();
|
|
455
442
|
|
|
456
|
-
|
|
457
|
-
const tokenOwner = this._ownerOf(tokenId);
|
|
458
|
-
if (tokenOwner !== owner) throw new Revert('Not token owner');
|
|
443
|
+
if (owner === operator) throw new Revert('Cannot approve self');
|
|
459
444
|
|
|
460
|
-
this.
|
|
445
|
+
this._verifySetApprovalForAllSignature(owner, operator, approved, deadline, signature);
|
|
461
446
|
|
|
462
|
-
this.
|
|
447
|
+
this._setApprovalForAll(owner, operator, approved);
|
|
463
448
|
|
|
464
449
|
return new BytesWriter(0);
|
|
465
450
|
}
|
|
@@ -500,16 +485,6 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
500
485
|
return w;
|
|
501
486
|
}
|
|
502
487
|
|
|
503
|
-
@method({ name: 'owner', type: ABIDataTypes.ADDRESS })
|
|
504
|
-
@returns({ name: 'nonce', type: ABIDataTypes.UINT256 })
|
|
505
|
-
public getTransferNonce(calldata: Calldata): BytesWriter {
|
|
506
|
-
const owner = calldata.readAddress();
|
|
507
|
-
const nonce = this._transferNonceMap.get(owner);
|
|
508
|
-
const w = new BytesWriter(U256_BYTE_LENGTH);
|
|
509
|
-
w.writeU256(nonce);
|
|
510
|
-
return w;
|
|
511
|
-
}
|
|
512
|
-
|
|
513
488
|
@method({ name: 'owner', type: ABIDataTypes.ADDRESS })
|
|
514
489
|
@returns({ name: 'nonce', type: ABIDataTypes.UINT256 })
|
|
515
490
|
public getApproveNonce(calldata: Calldata): BytesWriter {
|
|
@@ -546,7 +521,6 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
546
521
|
{ name: 'description', type: ABIDataTypes.STRING },
|
|
547
522
|
{ name: 'website', type: ABIDataTypes.STRING },
|
|
548
523
|
{ name: 'totalSupply', type: ABIDataTypes.UINT256 },
|
|
549
|
-
{ name: 'maximumSupply', type: ABIDataTypes.UINT256 },
|
|
550
524
|
{ name: 'domainSeparator', type: ABIDataTypes.BYTES32 },
|
|
551
525
|
)
|
|
552
526
|
public metadata(_: Calldata): BytesWriter {
|
|
@@ -657,7 +631,7 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
657
631
|
this.createTransferEvent(owner, Address.zero(), tokenId);
|
|
658
632
|
}
|
|
659
633
|
|
|
660
|
-
protected _transfer(from: Address, to: Address, tokenId: u256): void {
|
|
634
|
+
protected _transfer(from: Address, to: Address, tokenId: u256, data: Uint8Array): void {
|
|
661
635
|
// Skip self-transfers
|
|
662
636
|
if (from === to) return;
|
|
663
637
|
|
|
@@ -699,6 +673,11 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
699
673
|
// Transfer ownership
|
|
700
674
|
this.ownerOfMap.set(tokenId, this._u256FromAddress(to));
|
|
701
675
|
|
|
676
|
+
// External call happens after all state changes
|
|
677
|
+
if (Blockchain.isContract(to)) {
|
|
678
|
+
this._checkOnOP721Received(from, to, tokenId, data);
|
|
679
|
+
}
|
|
680
|
+
|
|
702
681
|
this.createTransferEvent(from, to, tokenId);
|
|
703
682
|
}
|
|
704
683
|
|
|
@@ -774,10 +753,10 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
774
753
|
): void {
|
|
775
754
|
const calldata = new BytesWriter(
|
|
776
755
|
SELECTOR_BYTE_LENGTH +
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
756
|
+
ADDRESS_BYTE_LENGTH * 2 +
|
|
757
|
+
U256_BYTE_LENGTH +
|
|
758
|
+
U32_BYTE_LENGTH +
|
|
759
|
+
data.length,
|
|
781
760
|
);
|
|
782
761
|
calldata.writeSelector(ON_OP721_RECEIVED_SELECTOR);
|
|
783
762
|
calldata.writeAddress(Blockchain.tx.sender);
|
|
@@ -796,9 +775,9 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
796
775
|
}
|
|
797
776
|
}
|
|
798
777
|
|
|
799
|
-
protected
|
|
778
|
+
protected _verifyApproveSignature(
|
|
800
779
|
owner: Address,
|
|
801
|
-
|
|
780
|
+
spender: Address,
|
|
802
781
|
tokenId: u256,
|
|
803
782
|
deadline: u64,
|
|
804
783
|
signature: Uint8Array,
|
|
@@ -810,38 +789,26 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
810
789
|
throw new Revert('Signature expired');
|
|
811
790
|
}
|
|
812
791
|
|
|
813
|
-
const nonce = this.
|
|
792
|
+
const nonce = this._approveNonceMap.get(owner);
|
|
814
793
|
|
|
815
794
|
const structWriter = new BytesWriter(
|
|
816
795
|
32 + ADDRESS_BYTE_LENGTH * 2 + U256_BYTE_LENGTH * 2 + U64_BYTE_LENGTH,
|
|
817
796
|
);
|
|
818
|
-
structWriter.writeBytesU8Array(
|
|
797
|
+
structWriter.writeBytesU8Array(OP721_APPROVAL_TYPE_HASH);
|
|
819
798
|
structWriter.writeAddress(owner);
|
|
820
|
-
structWriter.writeAddress(
|
|
799
|
+
structWriter.writeAddress(spender);
|
|
821
800
|
structWriter.writeU256(tokenId);
|
|
822
801
|
structWriter.writeU256(nonce);
|
|
823
802
|
structWriter.writeU64(deadline);
|
|
824
803
|
|
|
825
804
|
const structHash = sha256(structWriter.getBuffer());
|
|
826
|
-
|
|
827
|
-
const messageWriter = new BytesWriter(2 + 32 + 32);
|
|
828
|
-
messageWriter.writeU16(0x1901);
|
|
829
|
-
messageWriter.writeBytes(this._buildDomainSeparator());
|
|
830
|
-
messageWriter.writeBytes(structHash);
|
|
831
|
-
|
|
832
|
-
const hash = sha256(messageWriter.getBuffer());
|
|
833
|
-
|
|
834
|
-
if (!Blockchain.verifySchnorrSignature(owner, signature, hash)) {
|
|
835
|
-
throw new Revert('Invalid signature');
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
this._transferNonceMap.set(owner, SafeMath.add(nonce, u256.One));
|
|
805
|
+
this._verifySignature(structHash, owner, signature, nonce);
|
|
839
806
|
}
|
|
840
807
|
|
|
841
|
-
protected
|
|
808
|
+
protected _verifySetApprovalForAllSignature(
|
|
842
809
|
owner: Address,
|
|
843
810
|
spender: Address,
|
|
844
|
-
|
|
811
|
+
approved: boolean,
|
|
845
812
|
deadline: u64,
|
|
846
813
|
signature: Uint8Array,
|
|
847
814
|
): void {
|
|
@@ -857,15 +824,23 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
857
824
|
const structWriter = new BytesWriter(
|
|
858
825
|
32 + ADDRESS_BYTE_LENGTH * 2 + U256_BYTE_LENGTH * 2 + U64_BYTE_LENGTH,
|
|
859
826
|
);
|
|
860
|
-
structWriter.writeBytesU8Array(
|
|
827
|
+
structWriter.writeBytesU8Array(OP721_APPROVAL_TYPE_HASH);
|
|
861
828
|
structWriter.writeAddress(owner);
|
|
862
829
|
structWriter.writeAddress(spender);
|
|
863
|
-
structWriter.
|
|
830
|
+
structWriter.writeBoolean(approved);
|
|
864
831
|
structWriter.writeU256(nonce);
|
|
865
832
|
structWriter.writeU64(deadline);
|
|
866
833
|
|
|
867
834
|
const structHash = sha256(structWriter.getBuffer());
|
|
835
|
+
this._verifySignature(structHash, owner, signature, nonce);
|
|
836
|
+
}
|
|
868
837
|
|
|
838
|
+
protected _verifySignature(
|
|
839
|
+
structHash: Uint8Array,
|
|
840
|
+
owner: Address,
|
|
841
|
+
signature: Uint8Array,
|
|
842
|
+
nonce: u256,
|
|
843
|
+
): void {
|
|
869
844
|
const messageWriter = new BytesWriter(2 + 32 + 32);
|
|
870
845
|
messageWriter.writeU16(0x1901);
|
|
871
846
|
messageWriter.writeBytes(this._buildDomainSeparator());
|
|
@@ -13,7 +13,7 @@ export interface IOP721 {
|
|
|
13
13
|
ownerOf(calldata: Calldata): BytesWriter;
|
|
14
14
|
|
|
15
15
|
// Transfer functions
|
|
16
|
-
|
|
16
|
+
safeTransfer(calldata: Calldata): BytesWriter;
|
|
17
17
|
safeTransferFrom(calldata: Calldata): BytesWriter;
|
|
18
18
|
|
|
19
19
|
// Approval functions
|
|
@@ -21,9 +21,10 @@ export interface IOP721 {
|
|
|
21
21
|
getApproved(calldata: Calldata): BytesWriter;
|
|
22
22
|
setApprovalForAll(calldata: Calldata): BytesWriter;
|
|
23
23
|
isApprovedForAll(calldata: Calldata): BytesWriter;
|
|
24
|
+
approveBySignature(calldata: Calldata): BytesWriter;
|
|
25
|
+
setApprovalForAllBySignature(calldata: Calldata): BytesWriter;
|
|
24
26
|
|
|
25
27
|
// Advanced functions
|
|
26
28
|
burn(calldata: Calldata): BytesWriter;
|
|
27
|
-
transferBySignature(calldata: Calldata): BytesWriter;
|
|
28
29
|
domainSeparator(calldata: Calldata): BytesWriter;
|
|
29
30
|
}
|
|
@@ -7,12 +7,17 @@ export class TransactionInput {
|
|
|
7
7
|
public readonly txId: Uint8Array,
|
|
8
8
|
public readonly outputIndex: u16,
|
|
9
9
|
public readonly scriptSig: Uint8Array,
|
|
10
|
+
public readonly witnesses: Uint8Array[] | null,
|
|
10
11
|
public readonly coinbase: Uint8Array | null,
|
|
11
12
|
) {}
|
|
12
13
|
|
|
13
14
|
public get isCoinbase(): boolean {
|
|
14
15
|
return (this.flags & TransactionInputFlags.hasCoinbase) !== 0;
|
|
15
16
|
}
|
|
17
|
+
|
|
18
|
+
public get hasWitnesses(): boolean {
|
|
19
|
+
return (this.flags & TransactionInputFlags.hasWitnesses) !== 0;
|
|
20
|
+
}
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
@final
|
|
@@ -35,7 +35,14 @@ export class TransactionDecoder {
|
|
|
35
35
|
? buffer.readBytesWithLength()
|
|
36
36
|
: null;
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
const witnesses: Uint8Array[] | null = this.hasFlag(
|
|
39
|
+
flags,
|
|
40
|
+
TransactionInputFlags.hasWitnesses,
|
|
41
|
+
)
|
|
42
|
+
? buffer.readArrayOfBuffer()
|
|
43
|
+
: null;
|
|
44
|
+
|
|
45
|
+
return new TransactionInput(flags, txId, outputIndex, scriptSig, witnesses, coinbase);
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
private decodeOutput(buffer: BytesReader): TransactionOutput {
|