@btc-vision/btc-runtime 1.9.12 → 1.9.13
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/constants/Exports.ts +7 -1
- package/runtime/contracts/OP721.ts +22 -20
- package/runtime/index.ts +0 -1
- package/runtime/contracts/OP1155.ts +0 -1042
package/package.json
CHANGED
|
@@ -64,7 +64,13 @@ export const OP712_VERSION_HASH: u8[] = [
|
|
|
64
64
|
// sha256("OP721Approval(address owner,address spender,uint256 tokenId,uint256 nonce,uint64 deadline)")
|
|
65
65
|
export const OP721_APPROVAL_TYPE_HASH: u8[] = [
|
|
66
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
|
|
67
|
+
0x55, 0xe7, 0xb8, 0x6d, 0xc6, 0x8c, 0x62, 0x42, 0x82, 0x1c, 0xbc, 0xac, 0xa2, 0xd8, 0x79, 0xde,
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
// sha256("OP721ApprovalForAll(address owner,address spender,bool approved,uint256 nonce,uint64 deadline)")
|
|
71
|
+
export const OP721_APPROVAL_FOR_ALL_TYPE_HASH: u8[] = [
|
|
72
|
+
0x4f, 0x48, 0x06, 0x5d, 0x9e, 0xf1, 0x45, 0x25, 0x6b, 0xf7, 0x7f, 0xd2, 0xe5, 0x8b, 0x79, 0xe6,
|
|
73
|
+
0xf6, 0x0c, 0xd0, 0xd3, 0x47, 0x70, 0x14, 0x32, 0x34, 0x50, 0xc9, 0x65, 0xb7, 0x4b, 0x80, 0xed,
|
|
68
74
|
];
|
|
69
75
|
|
|
70
76
|
// sha256("OP721Transfer(address from,address to,uint256 tokenId,uint256 nonce,uint64 deadline)")
|
|
@@ -14,6 +14,7 @@ import { Revert } from '../types/Revert';
|
|
|
14
14
|
import { SafeMath } from '../types/SafeMath';
|
|
15
15
|
import {
|
|
16
16
|
ADDRESS_BYTE_LENGTH,
|
|
17
|
+
BOOLEAN_BYTE_LENGTH,
|
|
17
18
|
SELECTOR_BYTE_LENGTH,
|
|
18
19
|
U256_BYTE_LENGTH,
|
|
19
20
|
U32_BYTE_LENGTH,
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
ON_OP721_RECEIVED_SELECTOR,
|
|
30
31
|
OP712_DOMAIN_TYPE_HASH,
|
|
31
32
|
OP712_VERSION_HASH,
|
|
33
|
+
OP721_APPROVAL_FOR_ALL_TYPE_HASH,
|
|
32
34
|
OP721_APPROVAL_TYPE_HASH,
|
|
33
35
|
} from '../constants/Exports';
|
|
34
36
|
|
|
@@ -339,19 +341,6 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
339
341
|
const operator = calldata.readAddress();
|
|
340
342
|
const tokenId = calldata.readU256();
|
|
341
343
|
|
|
342
|
-
// Validate to address
|
|
343
|
-
if (operator === Address.zero()) throw new Revert('Cannot approve zero address');
|
|
344
|
-
|
|
345
|
-
const owner = this._ownerOf(tokenId);
|
|
346
|
-
if (operator === owner) throw new Revert('Approval to current owner');
|
|
347
|
-
|
|
348
|
-
if (
|
|
349
|
-
owner !== Blockchain.tx.sender &&
|
|
350
|
-
!this._isApprovedForAll(owner, Blockchain.tx.sender)
|
|
351
|
-
) {
|
|
352
|
-
throw new Revert('Not authorized to approve');
|
|
353
|
-
}
|
|
354
|
-
|
|
355
344
|
this._approve(operator, tokenId);
|
|
356
345
|
|
|
357
346
|
return new BytesWriter(0);
|
|
@@ -673,18 +662,31 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
673
662
|
// Transfer ownership
|
|
674
663
|
this.ownerOfMap.set(tokenId, this._u256FromAddress(to));
|
|
675
664
|
|
|
665
|
+
this.createTransferEvent(from, to, tokenId);
|
|
666
|
+
|
|
676
667
|
// External call happens after all state changes
|
|
677
668
|
if (Blockchain.isContract(to)) {
|
|
678
669
|
this._checkOnOP721Received(from, to, tokenId, data);
|
|
679
670
|
}
|
|
680
|
-
|
|
681
|
-
this.createTransferEvent(from, to, tokenId);
|
|
682
671
|
}
|
|
683
672
|
|
|
684
|
-
protected _approve(
|
|
685
|
-
|
|
673
|
+
protected _approve(operator: Address, tokenId: u256): void {
|
|
674
|
+
// Validate to address
|
|
675
|
+
if (operator === Address.zero()) throw new Revert('Cannot approve zero address');
|
|
676
|
+
|
|
686
677
|
const owner = this._ownerOf(tokenId);
|
|
687
|
-
|
|
678
|
+
if (operator === owner) throw new Revert('Approval to current owner');
|
|
679
|
+
|
|
680
|
+
if (
|
|
681
|
+
owner !== Blockchain.tx.sender &&
|
|
682
|
+
!this._isApprovedForAll(owner, Blockchain.tx.sender)
|
|
683
|
+
) {
|
|
684
|
+
throw new Revert('Not authorized to approve');
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
this.tokenApprovalMap.set(tokenId, this._u256FromAddress(operator));
|
|
688
|
+
|
|
689
|
+
this.createApprovedEvent(owner, operator, tokenId);
|
|
688
690
|
}
|
|
689
691
|
|
|
690
692
|
protected _setApprovalForAll(owner: Address, operator: Address, approved: boolean): void {
|
|
@@ -822,9 +824,9 @@ export abstract class OP721 extends ReentrancyGuard implements IOP721 {
|
|
|
822
824
|
const nonce = this._approveNonceMap.get(owner);
|
|
823
825
|
|
|
824
826
|
const structWriter = new BytesWriter(
|
|
825
|
-
32 + ADDRESS_BYTE_LENGTH * 2 +
|
|
827
|
+
32 + ADDRESS_BYTE_LENGTH * 2 + BOOLEAN_BYTE_LENGTH + U256_BYTE_LENGTH + U64_BYTE_LENGTH,
|
|
826
828
|
);
|
|
827
|
-
structWriter.writeBytesU8Array(
|
|
829
|
+
structWriter.writeBytesU8Array(OP721_APPROVAL_FOR_ALL_TYPE_HASH);
|
|
828
830
|
structWriter.writeAddress(owner);
|
|
829
831
|
structWriter.writeAddress(spender);
|
|
830
832
|
structWriter.writeBoolean(approved);
|
package/runtime/index.ts
CHANGED
|
@@ -101,7 +101,6 @@ export * from './script/Segwit';
|
|
|
101
101
|
export * from './constants/Exports';
|
|
102
102
|
export * from './contracts/OP721';
|
|
103
103
|
export * from './contracts/interfaces/IOP721';
|
|
104
|
-
export * from './contracts/OP1155';
|
|
105
104
|
export * from './contracts/interfaces/IOP1155';
|
|
106
105
|
export * from './contracts/interfaces/OP721InitParameters';
|
|
107
106
|
export * from './contracts/ReentrancyGuard';
|
|
@@ -1,1042 +0,0 @@
|
|
|
1
|
-
import { u256 } from '@btc-vision/as-bignum/assembly';
|
|
2
|
-
import { BytesWriter } from '../buffer/BytesWriter';
|
|
3
|
-
import { Blockchain } from '../env';
|
|
4
|
-
import { sha256, sha256String } from '../env/global';
|
|
5
|
-
import { encodePointerUnknownLength } from '../math/abi';
|
|
6
|
-
import { EMPTY_POINTER } from '../math/bytes';
|
|
7
|
-
import { MapOfMap } from '../memory/MapOfMap';
|
|
8
|
-
import { AddressMemoryMap } from '../memory/AddressMemoryMap';
|
|
9
|
-
import { StoredString } from '../storage/StoredString';
|
|
10
|
-
import { StoredU256 } from '../storage/StoredU256';
|
|
11
|
-
import { Calldata } from '../types';
|
|
12
|
-
import { Address } from '../types/Address';
|
|
13
|
-
import { Revert } from '../types/Revert';
|
|
14
|
-
import { SafeMath } from '../types/SafeMath';
|
|
15
|
-
import {
|
|
16
|
-
ADDRESS_BYTE_LENGTH,
|
|
17
|
-
SELECTOR_BYTE_LENGTH,
|
|
18
|
-
U256_BYTE_LENGTH,
|
|
19
|
-
U32_BYTE_LENGTH,
|
|
20
|
-
U64_BYTE_LENGTH,
|
|
21
|
-
} from '../utils';
|
|
22
|
-
|
|
23
|
-
import { IOP1155 } from './interfaces/IOP1155';
|
|
24
|
-
import { OP1155InitParameters } from './interfaces/OP1155InitParameters';
|
|
25
|
-
import {
|
|
26
|
-
ApprovedForAllEvent,
|
|
27
|
-
TransferredBatchEvent,
|
|
28
|
-
TransferredSingleEvent,
|
|
29
|
-
URIEvent,
|
|
30
|
-
} from '../events/predefined';
|
|
31
|
-
import {
|
|
32
|
-
INTERFACE_ID_ERC165,
|
|
33
|
-
INTERFACE_ID_OP1155,
|
|
34
|
-
INTERFACE_ID_OP1155_METADATA_URI,
|
|
35
|
-
ON_OP1155_BATCH_RECEIVED_MAGIC,
|
|
36
|
-
ON_OP1155_RECEIVED_MAGIC,
|
|
37
|
-
OP1155_BATCH_TRANSFER_TYPE_HASH,
|
|
38
|
-
OP1155_TRANSFER_TYPE_HASH,
|
|
39
|
-
OP712_DOMAIN_TYPE_HASH,
|
|
40
|
-
OP712_VERSION_HASH,
|
|
41
|
-
} from '../constants/Exports';
|
|
42
|
-
import { ReentrancyGuard } from './ReentrancyGuard';
|
|
43
|
-
|
|
44
|
-
// Storage pointers
|
|
45
|
-
const namePointer: u16 = Blockchain.nextPointer;
|
|
46
|
-
const symbolPointer: u16 = Blockchain.nextPointer;
|
|
47
|
-
const baseUriPointer: u16 = Blockchain.nextPointer;
|
|
48
|
-
const balanceMapPointer: u16 = Blockchain.nextPointer;
|
|
49
|
-
const operatorApprovalMapPointer: u16 = Blockchain.nextPointer;
|
|
50
|
-
const totalSupplyMapPointer: u16 = Blockchain.nextPointer;
|
|
51
|
-
const nonceMapPointer: u16 = Blockchain.nextPointer;
|
|
52
|
-
const initializedPointer: u16 = Blockchain.nextPointer;
|
|
53
|
-
|
|
54
|
-
export abstract class OP1155 extends ReentrancyGuard implements IOP1155 {
|
|
55
|
-
// Nested mapping: account -> (id -> balance)
|
|
56
|
-
protected readonly balances: MapOfMap<u256>;
|
|
57
|
-
|
|
58
|
-
// Nested mapping: account -> (operator -> approved)
|
|
59
|
-
protected readonly operatorApprovals: MapOfMap<boolean>;
|
|
60
|
-
|
|
61
|
-
// Nonce mapping for signatures
|
|
62
|
-
protected readonly _nonceMap: AddressMemoryMap;
|
|
63
|
-
|
|
64
|
-
// Metadata
|
|
65
|
-
protected readonly _name: StoredString;
|
|
66
|
-
protected readonly _symbol: StoredString;
|
|
67
|
-
protected readonly _baseUri: StoredString;
|
|
68
|
-
protected readonly _initialized: StoredU256;
|
|
69
|
-
|
|
70
|
-
public constructor() {
|
|
71
|
-
super();
|
|
72
|
-
|
|
73
|
-
this.balances = new MapOfMap<u256>(balanceMapPointer);
|
|
74
|
-
this.operatorApprovals = new MapOfMap<boolean>(operatorApprovalMapPointer);
|
|
75
|
-
this._nonceMap = new AddressMemoryMap(nonceMapPointer);
|
|
76
|
-
|
|
77
|
-
this._name = new StoredString(namePointer, 0);
|
|
78
|
-
this._symbol = new StoredString(symbolPointer, 1);
|
|
79
|
-
this._baseUri = new StoredString(baseUriPointer, 0);
|
|
80
|
-
this._initialized = new StoredU256(initializedPointer, EMPTY_POINTER);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
public get name(): string {
|
|
84
|
-
const nameValue = this._name.value;
|
|
85
|
-
if (!nameValue || nameValue.length === 0) throw new Revert('Name not set');
|
|
86
|
-
return nameValue;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
public get symbol(): string {
|
|
90
|
-
const symbolValue = this._symbol.value;
|
|
91
|
-
if (!symbolValue || symbolValue.length === 0) throw new Revert('Symbol not set');
|
|
92
|
-
return symbolValue;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
public instantiate(
|
|
96
|
-
params: OP1155InitParameters,
|
|
97
|
-
skipDeployerVerification: boolean = false,
|
|
98
|
-
): void {
|
|
99
|
-
if (!this._initialized.value.isZero()) throw new Revert('Already initialized');
|
|
100
|
-
if (!skipDeployerVerification) this.onlyDeployer(Blockchain.tx.sender);
|
|
101
|
-
|
|
102
|
-
if (params.name.length == 0) throw new Revert('Name cannot be empty');
|
|
103
|
-
if (params.symbol.length == 0) throw new Revert('Symbol cannot be empty');
|
|
104
|
-
|
|
105
|
-
if (params.baseUri.length > 0 && !params.baseUri.includes('{id}')) {
|
|
106
|
-
throw new Revert('Base URI must contain {id} placeholder or be empty');
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
this._name.value = params.name;
|
|
110
|
-
this._symbol.value = params.symbol;
|
|
111
|
-
this._baseUri.value = params.baseUri;
|
|
112
|
-
this._initialized.value = u256.One;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
@method('name')
|
|
116
|
-
@returns({ name: 'name', type: ABIDataTypes.STRING })
|
|
117
|
-
public fn_name(_: Calldata): BytesWriter {
|
|
118
|
-
const w = new BytesWriter(String.UTF8.byteLength(this.name) + 4);
|
|
119
|
-
w.writeStringWithLength(this.name);
|
|
120
|
-
return w;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
@method('symbol')
|
|
124
|
-
@returns({ name: 'symbol', type: ABIDataTypes.STRING })
|
|
125
|
-
public fn_symbol(_: Calldata): BytesWriter {
|
|
126
|
-
const w = new BytesWriter(String.UTF8.byteLength(this.symbol) + 4);
|
|
127
|
-
w.writeStringWithLength(this.symbol);
|
|
128
|
-
return w;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
@method({ name: 'interfaceId', type: ABIDataTypes.BYTES4 })
|
|
132
|
-
@returns({ name: 'supported', type: ABIDataTypes.BOOL })
|
|
133
|
-
public supportsInterface(calldata: Calldata): BytesWriter {
|
|
134
|
-
const interfaceId = calldata.readU32();
|
|
135
|
-
|
|
136
|
-
let supported = false;
|
|
137
|
-
if (
|
|
138
|
-
interfaceId === INTERFACE_ID_ERC165 ||
|
|
139
|
-
interfaceId === INTERFACE_ID_OP1155 ||
|
|
140
|
-
interfaceId === INTERFACE_ID_OP1155_METADATA_URI
|
|
141
|
-
) {
|
|
142
|
-
supported = true;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
const w = new BytesWriter(1);
|
|
146
|
-
w.writeBoolean(supported);
|
|
147
|
-
return w;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
@method(
|
|
151
|
-
{ name: 'account', type: ABIDataTypes.ADDRESS },
|
|
152
|
-
{ name: 'id', type: ABIDataTypes.UINT256 },
|
|
153
|
-
)
|
|
154
|
-
@returns({ name: 'balance', type: ABIDataTypes.UINT256 })
|
|
155
|
-
public balanceOf(calldata: Calldata): BytesWriter {
|
|
156
|
-
const account = calldata.readAddress();
|
|
157
|
-
const id = calldata.readU256();
|
|
158
|
-
|
|
159
|
-
if (account.equals(Address.zero())) {
|
|
160
|
-
throw new Revert('Balance query for zero address');
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
const balance = this._balanceOf(account, id);
|
|
164
|
-
const w = new BytesWriter(U256_BYTE_LENGTH);
|
|
165
|
-
w.writeU256(balance);
|
|
166
|
-
return w;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
@method(
|
|
170
|
-
{ name: 'accounts', type: ABIDataTypes.ARRAY_OF_ADDRESSES },
|
|
171
|
-
{ name: 'ids', type: ABIDataTypes.ARRAY_OF_UINT256 },
|
|
172
|
-
)
|
|
173
|
-
@returns({ name: 'balances', type: ABIDataTypes.ARRAY_OF_UINT256 })
|
|
174
|
-
public balanceOfBatch(calldata: Calldata): BytesWriter {
|
|
175
|
-
const accountsLength = calldata.readU32();
|
|
176
|
-
if (accountsLength === 0) {
|
|
177
|
-
throw new Revert('Empty arrays');
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
const accounts: Address[] = [];
|
|
181
|
-
for (let i: u32 = 0; i < accountsLength; i++) {
|
|
182
|
-
accounts.push(calldata.readAddress());
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const idsLength = calldata.readU32();
|
|
186
|
-
const ids: u256[] = [];
|
|
187
|
-
for (let i: u32 = 0; i < idsLength; i++) {
|
|
188
|
-
ids.push(calldata.readU256());
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
if (accounts.length != ids.length) {
|
|
192
|
-
throw new Revert('Array length mismatch');
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const w = new BytesWriter(U32_BYTE_LENGTH + accounts.length * U256_BYTE_LENGTH);
|
|
196
|
-
w.writeU32(u32(accounts.length));
|
|
197
|
-
|
|
198
|
-
for (let i: i32 = 0; i < accounts.length; i++) {
|
|
199
|
-
if (accounts[i].equals(Address.zero())) {
|
|
200
|
-
throw new Revert('Balance query for zero address');
|
|
201
|
-
}
|
|
202
|
-
w.writeU256(this._balanceOf(accounts[i], ids[i]));
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return w;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
@method(
|
|
209
|
-
{ name: 'from', type: ABIDataTypes.ADDRESS },
|
|
210
|
-
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
211
|
-
{ name: 'id', type: ABIDataTypes.UINT256 },
|
|
212
|
-
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
213
|
-
{ name: 'data', type: ABIDataTypes.BYTES },
|
|
214
|
-
)
|
|
215
|
-
@emit('TransferredSingle')
|
|
216
|
-
public safeTransferFrom(calldata: Calldata): BytesWriter {
|
|
217
|
-
const from = calldata.readAddress();
|
|
218
|
-
const to = calldata.readAddress();
|
|
219
|
-
const id = calldata.readU256();
|
|
220
|
-
const amount = calldata.readU256();
|
|
221
|
-
const data = calldata.readBytesWithLength();
|
|
222
|
-
|
|
223
|
-
const operator = Blockchain.tx.sender;
|
|
224
|
-
|
|
225
|
-
// Check approval
|
|
226
|
-
if (!from.equals(operator) && !this._isApprovedForAll(from, operator)) {
|
|
227
|
-
throw new Revert('Caller is not owner nor approved');
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
this._safeTransferFrom(operator, from, to, id, amount, data);
|
|
231
|
-
|
|
232
|
-
return new BytesWriter(0);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
@method(
|
|
236
|
-
{ name: 'from', type: ABIDataTypes.ADDRESS },
|
|
237
|
-
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
238
|
-
{ name: 'ids', type: ABIDataTypes.ARRAY_OF_UINT256 },
|
|
239
|
-
{ name: 'amounts', type: ABIDataTypes.ARRAY_OF_UINT256 },
|
|
240
|
-
{ name: 'data', type: ABIDataTypes.BYTES },
|
|
241
|
-
)
|
|
242
|
-
@emit('TransferredBatch')
|
|
243
|
-
public safeBatchTransferFrom(calldata: Calldata): BytesWriter {
|
|
244
|
-
const from = calldata.readAddress();
|
|
245
|
-
const to = calldata.readAddress();
|
|
246
|
-
|
|
247
|
-
const idsLength = calldata.readU32();
|
|
248
|
-
if (idsLength === 0) {
|
|
249
|
-
throw new Revert('Empty arrays');
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const ids: u256[] = [];
|
|
253
|
-
for (let i: u32 = 0; i < idsLength; i++) {
|
|
254
|
-
ids.push(calldata.readU256());
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const amountsLength = calldata.readU32();
|
|
258
|
-
const amounts: u256[] = [];
|
|
259
|
-
for (let i: u32 = 0; i < amountsLength; i++) {
|
|
260
|
-
amounts.push(calldata.readU256());
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
const data = calldata.readBytesWithLength();
|
|
264
|
-
|
|
265
|
-
const operator = Blockchain.tx.sender;
|
|
266
|
-
|
|
267
|
-
// Check approval
|
|
268
|
-
if (!from.equals(operator) && !this._isApprovedForAll(from, operator)) {
|
|
269
|
-
throw new Revert('Caller is not owner nor approved');
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
this._safeBatchTransferFrom(operator, from, to, ids, amounts, data);
|
|
273
|
-
|
|
274
|
-
return new BytesWriter(0);
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
@method(
|
|
278
|
-
{ name: 'operator', type: ABIDataTypes.ADDRESS },
|
|
279
|
-
{ name: 'approved', type: ABIDataTypes.BOOL },
|
|
280
|
-
)
|
|
281
|
-
@emit('ApprovedForAll')
|
|
282
|
-
public setApprovalForAll(calldata: Calldata): BytesWriter {
|
|
283
|
-
const operator = calldata.readAddress();
|
|
284
|
-
const approved = calldata.readBoolean();
|
|
285
|
-
|
|
286
|
-
this._setApprovalForAll(Blockchain.tx.sender, operator, approved);
|
|
287
|
-
|
|
288
|
-
return new BytesWriter(0);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
@method(
|
|
292
|
-
{ name: 'account', type: ABIDataTypes.ADDRESS },
|
|
293
|
-
{ name: 'operator', type: ABIDataTypes.ADDRESS },
|
|
294
|
-
)
|
|
295
|
-
@returns({ name: 'approved', type: ABIDataTypes.BOOL })
|
|
296
|
-
public isApprovedForAll(calldata: Calldata): BytesWriter {
|
|
297
|
-
const account = calldata.readAddress();
|
|
298
|
-
const operator = calldata.readAddress();
|
|
299
|
-
|
|
300
|
-
const approved: boolean = this._isApprovedForAll(account, operator);
|
|
301
|
-
const w = new BytesWriter(1);
|
|
302
|
-
w.writeBoolean(approved);
|
|
303
|
-
return w;
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
@method({ name: 'id', type: ABIDataTypes.UINT256 })
|
|
307
|
-
@returns({ name: 'uri', type: ABIDataTypes.STRING })
|
|
308
|
-
public uri(calldata: Calldata): BytesWriter {
|
|
309
|
-
const id = calldata.readU256();
|
|
310
|
-
const tokenURI = this._uri(id);
|
|
311
|
-
|
|
312
|
-
const w = new BytesWriter(String.UTF8.byteLength(tokenURI) + 4);
|
|
313
|
-
w.writeStringWithLength(tokenURI);
|
|
314
|
-
return w;
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
@method({ name: 'id', type: ABIDataTypes.UINT256 })
|
|
318
|
-
@returns({ name: 'totalSupply', type: ABIDataTypes.UINT256 })
|
|
319
|
-
public totalSupply(calldata: Calldata): BytesWriter {
|
|
320
|
-
const id = calldata.readU256();
|
|
321
|
-
const supply = this._totalSupply(id);
|
|
322
|
-
|
|
323
|
-
const w = new BytesWriter(U256_BYTE_LENGTH);
|
|
324
|
-
w.writeU256(supply);
|
|
325
|
-
return w;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
@method({ name: 'id', type: ABIDataTypes.UINT256 })
|
|
329
|
-
@returns({ name: 'exists', type: ABIDataTypes.BOOL })
|
|
330
|
-
public exists(calldata: Calldata): BytesWriter {
|
|
331
|
-
const id = calldata.readU256();
|
|
332
|
-
const exists = this._exists(id);
|
|
333
|
-
|
|
334
|
-
const w = new BytesWriter(1);
|
|
335
|
-
w.writeBoolean(exists);
|
|
336
|
-
return w;
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
@method(
|
|
340
|
-
{ name: 'account', type: ABIDataTypes.ADDRESS },
|
|
341
|
-
{ name: 'id', type: ABIDataTypes.UINT256 },
|
|
342
|
-
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
343
|
-
)
|
|
344
|
-
@emit('TransferredSingle')
|
|
345
|
-
public burn(calldata: Calldata): BytesWriter {
|
|
346
|
-
const account = calldata.readAddress();
|
|
347
|
-
const id = calldata.readU256();
|
|
348
|
-
const amount = calldata.readU256();
|
|
349
|
-
|
|
350
|
-
// Check if sender is authorized
|
|
351
|
-
if (
|
|
352
|
-
!account.equals(Blockchain.tx.sender) &&
|
|
353
|
-
!this._isApprovedForAll(account, Blockchain.tx.sender)
|
|
354
|
-
) {
|
|
355
|
-
throw new Revert('Not authorized to burn');
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
this._burn(account, id, amount);
|
|
359
|
-
return new BytesWriter(0);
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
@method(
|
|
363
|
-
{ name: 'account', type: ABIDataTypes.ADDRESS },
|
|
364
|
-
{ name: 'ids', type: ABIDataTypes.ARRAY_OF_UINT256 },
|
|
365
|
-
{ name: 'amounts', type: ABIDataTypes.ARRAY_OF_UINT256 },
|
|
366
|
-
)
|
|
367
|
-
@emit('TransferredBatch')
|
|
368
|
-
public burnBatch(calldata: Calldata): BytesWriter {
|
|
369
|
-
const account = calldata.readAddress();
|
|
370
|
-
|
|
371
|
-
const idsLength = calldata.readU32();
|
|
372
|
-
const ids: u256[] = [];
|
|
373
|
-
for (let i: u32 = 0; i < idsLength; i++) {
|
|
374
|
-
ids.push(calldata.readU256());
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
const amountsLength = calldata.readU32();
|
|
378
|
-
const amounts: u256[] = [];
|
|
379
|
-
for (let i: u32 = 0; i < amountsLength; i++) {
|
|
380
|
-
amounts.push(calldata.readU256());
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// Check if sender is authorized
|
|
384
|
-
if (
|
|
385
|
-
!account.equals(Blockchain.tx.sender) &&
|
|
386
|
-
!this._isApprovedForAll(account, Blockchain.tx.sender)
|
|
387
|
-
) {
|
|
388
|
-
throw new Revert('Not authorized to burn');
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
this._burnBatch(account, ids, amounts);
|
|
392
|
-
return new BytesWriter(0);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
@method(
|
|
396
|
-
{ name: 'from', type: ABIDataTypes.ADDRESS },
|
|
397
|
-
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
398
|
-
{ name: 'id', type: ABIDataTypes.UINT256 },
|
|
399
|
-
{ name: 'amount', type: ABIDataTypes.UINT256 },
|
|
400
|
-
{ name: 'deadline', type: ABIDataTypes.UINT64 },
|
|
401
|
-
{ name: 'signature', type: ABIDataTypes.BYTES },
|
|
402
|
-
)
|
|
403
|
-
@emit('TransferredSingle')
|
|
404
|
-
public transferBySignature(calldata: Calldata): BytesWriter {
|
|
405
|
-
const from = calldata.readAddress();
|
|
406
|
-
const to = calldata.readAddress();
|
|
407
|
-
const id = calldata.readU256();
|
|
408
|
-
const amount = calldata.readU256();
|
|
409
|
-
const deadline = calldata.readU64();
|
|
410
|
-
const signature = calldata.readBytesWithLength();
|
|
411
|
-
|
|
412
|
-
this._verifyTransferSignature(from, to, id, amount, deadline, signature);
|
|
413
|
-
this._safeTransferFrom(from, from, to, id, amount, new Uint8Array(0));
|
|
414
|
-
|
|
415
|
-
return new BytesWriter(0);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
@method(
|
|
419
|
-
{ name: 'from', type: ABIDataTypes.ADDRESS },
|
|
420
|
-
{ name: 'to', type: ABIDataTypes.ADDRESS },
|
|
421
|
-
{ name: 'ids', type: ABIDataTypes.ARRAY_OF_UINT256 },
|
|
422
|
-
{ name: 'amounts', type: ABIDataTypes.ARRAY_OF_UINT256 },
|
|
423
|
-
{ name: 'deadline', type: ABIDataTypes.UINT64 },
|
|
424
|
-
{ name: 'signature', type: ABIDataTypes.BYTES },
|
|
425
|
-
)
|
|
426
|
-
@emit('TransferredBatch')
|
|
427
|
-
public batchTransferBySignature(calldata: Calldata): BytesWriter {
|
|
428
|
-
const from = calldata.readAddress();
|
|
429
|
-
const to = calldata.readAddress();
|
|
430
|
-
|
|
431
|
-
const idsLength = calldata.readU32();
|
|
432
|
-
const ids: u256[] = [];
|
|
433
|
-
for (let i: u32 = 0; i < idsLength; i++) {
|
|
434
|
-
ids.push(calldata.readU256());
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const amountsLength = calldata.readU32();
|
|
438
|
-
const amounts: u256[] = [];
|
|
439
|
-
for (let i: u32 = 0; i < amountsLength; i++) {
|
|
440
|
-
amounts.push(calldata.readU256());
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
const deadline = calldata.readU64();
|
|
444
|
-
const signature = calldata.readBytesWithLength();
|
|
445
|
-
|
|
446
|
-
this._verifyBatchTransferSignature(from, to, ids, amounts, deadline, signature);
|
|
447
|
-
this._safeBatchTransferFrom(from, from, to, ids, amounts, new Uint8Array(0));
|
|
448
|
-
|
|
449
|
-
return new BytesWriter(0);
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
@method()
|
|
453
|
-
@returns({ name: 'domainSeparator', type: ABIDataTypes.BYTES32 })
|
|
454
|
-
public domainSeparator(_: Calldata): BytesWriter {
|
|
455
|
-
const w = new BytesWriter(32);
|
|
456
|
-
w.writeBytes(this._buildDomainSeparator());
|
|
457
|
-
return w;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
@method({ name: 'owner', type: ABIDataTypes.ADDRESS })
|
|
461
|
-
@returns({ name: 'nonce', type: ABIDataTypes.UINT256 })
|
|
462
|
-
public nonceOf(calldata: Calldata): BytesWriter {
|
|
463
|
-
const current = this._nonceMap.get(calldata.readAddress());
|
|
464
|
-
const w = new BytesWriter(U256_BYTE_LENGTH);
|
|
465
|
-
w.writeU256(current);
|
|
466
|
-
return w;
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// Internal functions
|
|
470
|
-
protected _balanceOf(account: Address, id: u256): u256 {
|
|
471
|
-
const accountBalances = this.balances.get(account);
|
|
472
|
-
const idKey = id.toUint8Array(true);
|
|
473
|
-
const balance = accountBalances.get(idKey);
|
|
474
|
-
return balance ? balance : u256.Zero;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
protected _isApprovedForAll(account: Address, operator: Address): boolean {
|
|
478
|
-
const accountApprovals = this.operatorApprovals.get(account);
|
|
479
|
-
return accountApprovals.get(operator);
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
protected _exists(id: u256): boolean {
|
|
483
|
-
return this._totalSupply(id) > u256.Zero;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
protected _setApprovalForAll(owner: Address, operator: Address, approved: boolean): void {
|
|
487
|
-
if (owner.equals(operator)) {
|
|
488
|
-
throw new Revert('Setting approval status for self');
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
const ownerApprovals = this.operatorApprovals.get(owner);
|
|
492
|
-
ownerApprovals.set(operator, approved);
|
|
493
|
-
|
|
494
|
-
this.emitEvent(new ApprovedForAllEvent(owner, operator, approved));
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
protected _safeTransferFrom(
|
|
498
|
-
operator: Address,
|
|
499
|
-
from: Address,
|
|
500
|
-
to: Address,
|
|
501
|
-
id: u256,
|
|
502
|
-
amount: u256,
|
|
503
|
-
data: Uint8Array,
|
|
504
|
-
): void {
|
|
505
|
-
if (amount.isZero()) {
|
|
506
|
-
throw new Revert('Transfer amount must be greater than zero');
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
if (to.equals(Address.zero())) {
|
|
510
|
-
throw new Revert('Transfer to zero address');
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
this._beforeTokenTransfer(operator, from, to, [id], [amount], data);
|
|
514
|
-
|
|
515
|
-
const idKey = id.toUint8Array(true);
|
|
516
|
-
|
|
517
|
-
// Update from balance
|
|
518
|
-
const fromBalances = this.balances.get(from);
|
|
519
|
-
const fromBalance = fromBalances.get(idKey);
|
|
520
|
-
|
|
521
|
-
if (!fromBalance || fromBalance < amount) {
|
|
522
|
-
throw new Revert('Insufficient balance for transfer');
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
fromBalances.set(idKey, SafeMath.sub(fromBalance, amount));
|
|
526
|
-
|
|
527
|
-
// Update to balance
|
|
528
|
-
const toBalances = this.balances.get(to);
|
|
529
|
-
const toBalance = toBalances.get(idKey);
|
|
530
|
-
const newToBalance = toBalance ? SafeMath.add(toBalance, amount) : amount;
|
|
531
|
-
toBalances.set(idKey, newToBalance);
|
|
532
|
-
|
|
533
|
-
// Call receiver hook if contract
|
|
534
|
-
this._doSafeTransferAcceptanceCheck(operator, from, to, id, amount, data);
|
|
535
|
-
|
|
536
|
-
this.emitEvent(new TransferredSingleEvent(operator, from, to, id, amount));
|
|
537
|
-
|
|
538
|
-
this._afterTokenTransfer(operator, from, to, [id], [amount], data);
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
protected _safeBatchTransferFrom(
|
|
542
|
-
operator: Address,
|
|
543
|
-
from: Address,
|
|
544
|
-
to: Address,
|
|
545
|
-
ids: u256[],
|
|
546
|
-
amounts: u256[],
|
|
547
|
-
data: Uint8Array,
|
|
548
|
-
): void {
|
|
549
|
-
if (ids.length != amounts.length) {
|
|
550
|
-
throw new Revert('Array length mismatch');
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
if (to.equals(Address.zero())) {
|
|
554
|
-
throw new Revert('Transfer to zero address');
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
this._beforeTokenTransfer(operator, from, to, ids, amounts, data);
|
|
558
|
-
|
|
559
|
-
const fromBalances = this.balances.get(from);
|
|
560
|
-
const toBalances = this.balances.get(to);
|
|
561
|
-
|
|
562
|
-
for (let i = 0; i < ids.length; i++) {
|
|
563
|
-
const id = ids[i];
|
|
564
|
-
const amount = amounts[i];
|
|
565
|
-
if (amount.isZero()) {
|
|
566
|
-
throw new Revert('Transfer amount must be greater than zero');
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
const idKey = id.toUint8Array(true);
|
|
570
|
-
|
|
571
|
-
const fromBalance = fromBalances.get(idKey);
|
|
572
|
-
if (!fromBalance || fromBalance < amount) {
|
|
573
|
-
throw new Revert('Insufficient balance for transfer');
|
|
574
|
-
}
|
|
575
|
-
|
|
576
|
-
fromBalances.set(idKey, SafeMath.sub(fromBalance, amount));
|
|
577
|
-
|
|
578
|
-
const toBalance = toBalances.get(idKey);
|
|
579
|
-
const newToBalance = toBalance ? SafeMath.add(toBalance, amount) : amount;
|
|
580
|
-
toBalances.set(idKey, newToBalance);
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
const maxBatchSize: i32 = 3;
|
|
584
|
-
for (let i: i32 = 0; i < ids.length; i += maxBatchSize) {
|
|
585
|
-
const endIndex = min(i + maxBatchSize, ids.length);
|
|
586
|
-
const batchIds = ids.slice(i, endIndex);
|
|
587
|
-
const batchAmounts = amounts.slice(i, endIndex);
|
|
588
|
-
this.emitEvent(new TransferredBatchEvent(operator, from, to, batchIds, batchAmounts));
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
this._afterTokenTransfer(operator, from, to, ids, amounts, data);
|
|
592
|
-
|
|
593
|
-
// Call receiver hook if contract
|
|
594
|
-
this._doSafeBatchTransferAcceptanceCheck(operator, from, to, ids, amounts, data);
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
protected _mint(to: Address, id: u256, amount: u256, data: Uint8Array): void {
|
|
598
|
-
if (to.equals(Address.zero())) {
|
|
599
|
-
throw new Revert('Mint to zero address');
|
|
600
|
-
}
|
|
601
|
-
|
|
602
|
-
if (amount.isZero()) {
|
|
603
|
-
throw new Revert('Transfer amount must be greater than zero');
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
const operator = Blockchain.tx.sender;
|
|
607
|
-
|
|
608
|
-
this._beforeTokenTransfer(operator, Address.zero(), to, [id], [amount], data);
|
|
609
|
-
|
|
610
|
-
const idKey = id.toUint8Array(true);
|
|
611
|
-
|
|
612
|
-
// Update balance
|
|
613
|
-
const toBalances = this.balances.get(to);
|
|
614
|
-
const toBalance = toBalances.get(idKey);
|
|
615
|
-
const newBalance = toBalance ? SafeMath.add(toBalance, amount) : amount;
|
|
616
|
-
toBalances.set(idKey, newBalance);
|
|
617
|
-
|
|
618
|
-
// Update total supply
|
|
619
|
-
const currentSupply = this._totalSupply(id);
|
|
620
|
-
this._setTotalSupply(id, SafeMath.add(currentSupply, amount));
|
|
621
|
-
|
|
622
|
-
this.emitEvent(new TransferredSingleEvent(operator, Address.zero(), to, id, amount));
|
|
623
|
-
|
|
624
|
-
this._afterTokenTransfer(operator, Address.zero(), to, [id], [amount], data);
|
|
625
|
-
|
|
626
|
-
// Call receiver hook if contract
|
|
627
|
-
this._doSafeTransferAcceptanceCheck(operator, Address.zero(), to, id, amount, data);
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
protected _mintBatch(to: Address, ids: u256[], amounts: u256[], data: Uint8Array): void {
|
|
631
|
-
if (to.equals(Address.zero())) {
|
|
632
|
-
throw new Revert('Mint to zero address');
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
if (ids.length != amounts.length) {
|
|
636
|
-
throw new Revert('Array length mismatch');
|
|
637
|
-
}
|
|
638
|
-
|
|
639
|
-
const operator = Blockchain.tx.sender;
|
|
640
|
-
|
|
641
|
-
this._beforeTokenTransfer(operator, Address.zero(), to, ids, amounts, data);
|
|
642
|
-
|
|
643
|
-
const toBalances = this.balances.get(to);
|
|
644
|
-
|
|
645
|
-
for (let i: i32 = 0; i < ids.length; i++) {
|
|
646
|
-
const id = ids[i];
|
|
647
|
-
const amount = amounts[i];
|
|
648
|
-
if (amount.isZero()) {
|
|
649
|
-
throw new Revert('Transfer amount must be greater than zero');
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
const idKey = id.toUint8Array(true);
|
|
653
|
-
|
|
654
|
-
const toBalance = toBalances.get(idKey);
|
|
655
|
-
const newBalance = toBalance ? SafeMath.add(toBalance, amount) : amount;
|
|
656
|
-
toBalances.set(idKey, newBalance);
|
|
657
|
-
|
|
658
|
-
// Update total supply
|
|
659
|
-
const currentSupply = this._totalSupply(id);
|
|
660
|
-
this._setTotalSupply(id, SafeMath.add(currentSupply, amount));
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
// Emit events in batches of 3 due to event size limit
|
|
664
|
-
const maxBatchSize = 3;
|
|
665
|
-
for (let i: i32 = 0; i < ids.length; i += maxBatchSize) {
|
|
666
|
-
const endIndex = min(i + maxBatchSize, ids.length);
|
|
667
|
-
const batchIds = ids.slice(i, endIndex);
|
|
668
|
-
const batchAmounts = amounts.slice(i, endIndex);
|
|
669
|
-
this.emitEvent(
|
|
670
|
-
new TransferredBatchEvent(operator, Address.zero(), to, batchIds, batchAmounts),
|
|
671
|
-
);
|
|
672
|
-
}
|
|
673
|
-
|
|
674
|
-
this._afterTokenTransfer(operator, Address.zero(), to, ids, amounts, data);
|
|
675
|
-
|
|
676
|
-
// Call receiver hook if contract
|
|
677
|
-
this._doSafeBatchTransferAcceptanceCheck(operator, Address.zero(), to, ids, amounts, data);
|
|
678
|
-
}
|
|
679
|
-
|
|
680
|
-
protected _burn(from: Address, id: u256, amount: u256): void {
|
|
681
|
-
if (from.equals(Address.zero())) {
|
|
682
|
-
throw new Revert('Burn from zero address');
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
if (amount.isZero()) {
|
|
686
|
-
throw new Revert('Transfer amount must be greater than zero');
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
const operator = Blockchain.tx.sender;
|
|
690
|
-
|
|
691
|
-
this._beforeTokenTransfer(
|
|
692
|
-
operator,
|
|
693
|
-
from,
|
|
694
|
-
Address.zero(),
|
|
695
|
-
[id],
|
|
696
|
-
[amount],
|
|
697
|
-
new Uint8Array(0),
|
|
698
|
-
);
|
|
699
|
-
|
|
700
|
-
const idKey = id.toUint8Array(true);
|
|
701
|
-
|
|
702
|
-
const fromBalances = this.balances.get(from);
|
|
703
|
-
const fromBalance = fromBalances.get(idKey);
|
|
704
|
-
|
|
705
|
-
if (!fromBalance || fromBalance < amount) {
|
|
706
|
-
throw new Revert('Burn amount exceeds balance');
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
fromBalances.set(idKey, SafeMath.sub(fromBalance, amount));
|
|
710
|
-
|
|
711
|
-
// Update total supply
|
|
712
|
-
const currentSupply = this._totalSupply(id);
|
|
713
|
-
this._setTotalSupply(id, SafeMath.sub(currentSupply, amount));
|
|
714
|
-
|
|
715
|
-
this.emitEvent(new TransferredSingleEvent(operator, from, Address.zero(), id, amount));
|
|
716
|
-
|
|
717
|
-
this._afterTokenTransfer(operator, from, Address.zero(), [id], [amount], new Uint8Array(0));
|
|
718
|
-
}
|
|
719
|
-
|
|
720
|
-
protected _burnBatch(from: Address, ids: u256[], amounts: u256[]): void {
|
|
721
|
-
if (from.equals(Address.zero())) {
|
|
722
|
-
throw new Revert('Burn from zero address');
|
|
723
|
-
}
|
|
724
|
-
|
|
725
|
-
if (ids.length != amounts.length) {
|
|
726
|
-
throw new Revert('Array length mismatch');
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
const operator = Blockchain.tx.sender;
|
|
730
|
-
|
|
731
|
-
this._beforeTokenTransfer(operator, from, Address.zero(), ids, amounts, new Uint8Array(0));
|
|
732
|
-
|
|
733
|
-
const fromBalances = this.balances.get(from);
|
|
734
|
-
|
|
735
|
-
for (let i: i32 = 0; i < ids.length; i++) {
|
|
736
|
-
const id = ids[i];
|
|
737
|
-
const amount = amounts[i];
|
|
738
|
-
if (amount.isZero()) {
|
|
739
|
-
throw new Revert('Transfer amount must be greater than zero');
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
const idKey = id.toUint8Array(true);
|
|
743
|
-
|
|
744
|
-
const fromBalance = fromBalances.get(idKey);
|
|
745
|
-
if (!fromBalance || fromBalance < amount) {
|
|
746
|
-
throw new Revert('Burn amount exceeds balance');
|
|
747
|
-
}
|
|
748
|
-
|
|
749
|
-
fromBalances.set(idKey, SafeMath.sub(fromBalance, amount));
|
|
750
|
-
|
|
751
|
-
// Update total supply
|
|
752
|
-
const currentSupply = this._totalSupply(id);
|
|
753
|
-
this._setTotalSupply(id, SafeMath.sub(currentSupply, amount));
|
|
754
|
-
}
|
|
755
|
-
|
|
756
|
-
const maxBatchSize: i32 = 3;
|
|
757
|
-
for (let i: i32 = 0; i < ids.length; i += maxBatchSize) {
|
|
758
|
-
const endIndex = min(i + maxBatchSize, ids.length);
|
|
759
|
-
const batchIds = ids.slice(i, endIndex);
|
|
760
|
-
const batchAmounts = amounts.slice(i, endIndex);
|
|
761
|
-
this.emitEvent(
|
|
762
|
-
new TransferredBatchEvent(operator, from, Address.zero(), batchIds, batchAmounts),
|
|
763
|
-
);
|
|
764
|
-
}
|
|
765
|
-
|
|
766
|
-
this._afterTokenTransfer(operator, from, Address.zero(), ids, amounts, new Uint8Array(0));
|
|
767
|
-
}
|
|
768
|
-
|
|
769
|
-
protected _setURI(newuri: string): void {
|
|
770
|
-
this._baseUri.value = newuri;
|
|
771
|
-
|
|
772
|
-
this.emitEvent(new URIEvent(newuri, u256.Zero));
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
protected _uri(id: u256): string {
|
|
776
|
-
const baseUri = this._baseUri.value;
|
|
777
|
-
if (!baseUri || baseUri.length === 0) {
|
|
778
|
-
return '';
|
|
779
|
-
}
|
|
780
|
-
|
|
781
|
-
// Replace {id} with actual token ID in hex format
|
|
782
|
-
const idHex = this._toHexString(id);
|
|
783
|
-
return baseUri.replace('{id}', idHex);
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
protected _totalSupply(id: u256): u256 {
|
|
787
|
-
const key = this._getTokenStorageKey(totalSupplyMapPointer, id);
|
|
788
|
-
const supplyBytes = Blockchain.getStorageAt(key);
|
|
789
|
-
if (supplyBytes.length === 0) return u256.Zero;
|
|
790
|
-
return u256.fromUint8ArrayBE(supplyBytes);
|
|
791
|
-
}
|
|
792
|
-
|
|
793
|
-
protected _setTotalSupply(id: u256, supply: u256): void {
|
|
794
|
-
const key = this._getTokenStorageKey(totalSupplyMapPointer, id);
|
|
795
|
-
Blockchain.setStorageAt(key, supply.toUint8Array(true));
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
// Signature verification
|
|
799
|
-
protected _verifyTransferSignature(
|
|
800
|
-
from: Address,
|
|
801
|
-
to: Address,
|
|
802
|
-
id: u256,
|
|
803
|
-
amount: u256,
|
|
804
|
-
deadline: u64,
|
|
805
|
-
signature: Uint8Array,
|
|
806
|
-
): void {
|
|
807
|
-
if (signature.length !== 64) {
|
|
808
|
-
throw new Revert('Invalid signature length');
|
|
809
|
-
}
|
|
810
|
-
if (Blockchain.block.number > deadline) {
|
|
811
|
-
throw new Revert('Signature expired');
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
const nonce = this._nonceMap.get(from);
|
|
815
|
-
|
|
816
|
-
const structWriter = new BytesWriter(
|
|
817
|
-
32 + ADDRESS_BYTE_LENGTH * 2 + U256_BYTE_LENGTH * 3 + U64_BYTE_LENGTH,
|
|
818
|
-
);
|
|
819
|
-
structWriter.writeBytesU8Array(OP1155_TRANSFER_TYPE_HASH);
|
|
820
|
-
structWriter.writeAddress(from);
|
|
821
|
-
structWriter.writeAddress(to);
|
|
822
|
-
structWriter.writeU256(id);
|
|
823
|
-
structWriter.writeU256(amount);
|
|
824
|
-
structWriter.writeU256(nonce);
|
|
825
|
-
structWriter.writeU64(deadline);
|
|
826
|
-
|
|
827
|
-
const structHash = sha256(structWriter.getBuffer());
|
|
828
|
-
|
|
829
|
-
const messageWriter = new BytesWriter(2 + 32 + 32);
|
|
830
|
-
messageWriter.writeU16(0x1901);
|
|
831
|
-
messageWriter.writeBytes(this._buildDomainSeparator());
|
|
832
|
-
messageWriter.writeBytes(structHash);
|
|
833
|
-
|
|
834
|
-
const hash = sha256(messageWriter.getBuffer());
|
|
835
|
-
|
|
836
|
-
if (!Blockchain.verifySchnorrSignature(from, signature, hash)) {
|
|
837
|
-
throw new Revert('Invalid signature');
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
this._nonceMap.set(from, SafeMath.add(nonce, u256.One));
|
|
841
|
-
}
|
|
842
|
-
|
|
843
|
-
protected _verifyBatchTransferSignature(
|
|
844
|
-
from: Address,
|
|
845
|
-
to: Address,
|
|
846
|
-
ids: u256[],
|
|
847
|
-
amounts: u256[],
|
|
848
|
-
deadline: u64,
|
|
849
|
-
signature: Uint8Array,
|
|
850
|
-
): void {
|
|
851
|
-
if (signature.length !== 64) {
|
|
852
|
-
throw new Revert('Invalid signature length');
|
|
853
|
-
}
|
|
854
|
-
|
|
855
|
-
if (Blockchain.block.number > deadline) {
|
|
856
|
-
throw new Revert('Signature expired');
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
const nonce = this._nonceMap.get(from);
|
|
860
|
-
|
|
861
|
-
// Calculate arrays hash
|
|
862
|
-
const idsWriter = new BytesWriter(ids.length * U256_BYTE_LENGTH);
|
|
863
|
-
for (let i: i32 = 0; i < ids.length; i++) {
|
|
864
|
-
idsWriter.writeU256(ids[i]);
|
|
865
|
-
}
|
|
866
|
-
|
|
867
|
-
const idsHash = sha256(idsWriter.getBuffer());
|
|
868
|
-
const amountsWriter = new BytesWriter(amounts.length * U256_BYTE_LENGTH);
|
|
869
|
-
|
|
870
|
-
for (let i: i32 = 0; i < amounts.length; i++) {
|
|
871
|
-
amountsWriter.writeU256(amounts[i]);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
const amountsHash = sha256(amountsWriter.getBuffer());
|
|
875
|
-
const structWriter = new BytesWriter(
|
|
876
|
-
32 * 4 + ADDRESS_BYTE_LENGTH * 2 + U256_BYTE_LENGTH + U64_BYTE_LENGTH,
|
|
877
|
-
);
|
|
878
|
-
|
|
879
|
-
structWriter.writeBytesU8Array(OP1155_BATCH_TRANSFER_TYPE_HASH);
|
|
880
|
-
structWriter.writeAddress(from);
|
|
881
|
-
structWriter.writeAddress(to);
|
|
882
|
-
structWriter.writeBytes(idsHash);
|
|
883
|
-
structWriter.writeBytes(amountsHash);
|
|
884
|
-
structWriter.writeU256(nonce);
|
|
885
|
-
structWriter.writeU64(deadline);
|
|
886
|
-
|
|
887
|
-
const structHash = sha256(structWriter.getBuffer());
|
|
888
|
-
const messageWriter = new BytesWriter(2 + 32 + 32);
|
|
889
|
-
messageWriter.writeU16(0x1901);
|
|
890
|
-
messageWriter.writeBytes(this._buildDomainSeparator());
|
|
891
|
-
messageWriter.writeBytes(structHash);
|
|
892
|
-
|
|
893
|
-
const hash = sha256(messageWriter.getBuffer());
|
|
894
|
-
if (!Blockchain.verifySchnorrSignature(from, signature, hash)) {
|
|
895
|
-
throw new Revert('Invalid signature');
|
|
896
|
-
}
|
|
897
|
-
|
|
898
|
-
this._nonceMap.set(from, SafeMath.add(nonce, u256.One));
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
protected _buildDomainSeparator(): Uint8Array {
|
|
902
|
-
const writer = new BytesWriter(32 * 5 + ADDRESS_BYTE_LENGTH);
|
|
903
|
-
writer.writeBytesU8Array(OP712_DOMAIN_TYPE_HASH);
|
|
904
|
-
writer.writeBytes(sha256String(this.name));
|
|
905
|
-
writer.writeBytesU8Array(OP712_VERSION_HASH);
|
|
906
|
-
writer.writeBytes(Blockchain.chainId);
|
|
907
|
-
writer.writeBytes(Blockchain.protocolId);
|
|
908
|
-
writer.writeAddress(this.address);
|
|
909
|
-
|
|
910
|
-
return sha256(writer.getBuffer());
|
|
911
|
-
}
|
|
912
|
-
|
|
913
|
-
// Hooks for extensions
|
|
914
|
-
protected _beforeTokenTransfer(
|
|
915
|
-
operator: Address,
|
|
916
|
-
from: Address,
|
|
917
|
-
to: Address,
|
|
918
|
-
ids: u256[],
|
|
919
|
-
amounts: u256[],
|
|
920
|
-
data: Uint8Array,
|
|
921
|
-
): void {
|
|
922
|
-
// Hook that can be overridden by extensions
|
|
923
|
-
}
|
|
924
|
-
|
|
925
|
-
protected _afterTokenTransfer(
|
|
926
|
-
operator: Address,
|
|
927
|
-
from: Address,
|
|
928
|
-
to: Address,
|
|
929
|
-
ids: u256[],
|
|
930
|
-
amounts: u256[],
|
|
931
|
-
data: Uint8Array,
|
|
932
|
-
): void {
|
|
933
|
-
// Hook that can be overridden by extensions
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
// Helper to get storage key for token-specific data
|
|
937
|
-
private _getTokenStorageKey(pointer: u16, id: u256): Uint8Array {
|
|
938
|
-
const idBytes = id.toUint8Array(true);
|
|
939
|
-
return encodePointerUnknownLength(pointer, idBytes);
|
|
940
|
-
}
|
|
941
|
-
|
|
942
|
-
// Helper to convert u256 to hex string
|
|
943
|
-
private _toHexString(value: u256): string {
|
|
944
|
-
const bytes = value.toUint8Array(true);
|
|
945
|
-
let hex: string = '';
|
|
946
|
-
|
|
947
|
-
// Convert each byte to hex
|
|
948
|
-
for (let i: i32 = 0; i < bytes.length; i++) {
|
|
949
|
-
const byte = bytes[i];
|
|
950
|
-
const hi = byte >> 4;
|
|
951
|
-
const lo = byte & 0x0f;
|
|
952
|
-
hex += hi < 10 ? String.fromCharCode(48 + hi) : String.fromCharCode(87 + hi);
|
|
953
|
-
hex += lo < 10 ? String.fromCharCode(48 + lo) : String.fromCharCode(87 + lo);
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
// Remove leading zeros, but keep at least one character
|
|
957
|
-
let startIndex = 0;
|
|
958
|
-
while (startIndex < hex.length - 1 && hex.charCodeAt(startIndex) === 48) {
|
|
959
|
-
startIndex++;
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
return startIndex > 0 ? hex.substring(startIndex) : hex;
|
|
963
|
-
}
|
|
964
|
-
|
|
965
|
-
private _doSafeTransferAcceptanceCheck(
|
|
966
|
-
operator: Address,
|
|
967
|
-
from: Address,
|
|
968
|
-
to: Address,
|
|
969
|
-
id: u256,
|
|
970
|
-
amount: u256,
|
|
971
|
-
data: Uint8Array,
|
|
972
|
-
): void {
|
|
973
|
-
if (Blockchain.isContract(to)) {
|
|
974
|
-
const calldata = new BytesWriter(
|
|
975
|
-
SELECTOR_BYTE_LENGTH +
|
|
976
|
-
ADDRESS_BYTE_LENGTH * 2 +
|
|
977
|
-
U256_BYTE_LENGTH * 2 +
|
|
978
|
-
U32_BYTE_LENGTH +
|
|
979
|
-
data.length,
|
|
980
|
-
);
|
|
981
|
-
calldata.writeSelector(ON_OP1155_RECEIVED_MAGIC);
|
|
982
|
-
calldata.writeAddress(operator);
|
|
983
|
-
calldata.writeAddress(from);
|
|
984
|
-
calldata.writeU256(id);
|
|
985
|
-
calldata.writeU256(amount);
|
|
986
|
-
calldata.writeBytesWithLength(data);
|
|
987
|
-
|
|
988
|
-
const response = Blockchain.call(to, calldata);
|
|
989
|
-
if (response.data.byteLength < SELECTOR_BYTE_LENGTH) {
|
|
990
|
-
throw new Revert('Transfer to non-OP1155Receiver implementer');
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
const retVal = response.data.readSelector();
|
|
994
|
-
if (retVal !== ON_OP1155_RECEIVED_MAGIC) {
|
|
995
|
-
throw new Revert('OP1155Receiver rejected tokens');
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
|
|
1000
|
-
private _doSafeBatchTransferAcceptanceCheck(
|
|
1001
|
-
operator: Address,
|
|
1002
|
-
from: Address,
|
|
1003
|
-
to: Address,
|
|
1004
|
-
ids: u256[],
|
|
1005
|
-
amounts: u256[],
|
|
1006
|
-
data: Uint8Array,
|
|
1007
|
-
): void {
|
|
1008
|
-
if (Blockchain.isContract(to)) {
|
|
1009
|
-
const calldata = new BytesWriter(
|
|
1010
|
-
SELECTOR_BYTE_LENGTH +
|
|
1011
|
-
ADDRESS_BYTE_LENGTH * 2 +
|
|
1012
|
-
U32_BYTE_LENGTH * 2 +
|
|
1013
|
-
ids.length * U256_BYTE_LENGTH +
|
|
1014
|
-
amounts.length * U256_BYTE_LENGTH +
|
|
1015
|
-
U32_BYTE_LENGTH +
|
|
1016
|
-
data.length,
|
|
1017
|
-
);
|
|
1018
|
-
calldata.writeSelector(ON_OP1155_BATCH_RECEIVED_MAGIC);
|
|
1019
|
-
calldata.writeAddress(operator);
|
|
1020
|
-
calldata.writeAddress(from);
|
|
1021
|
-
calldata.writeU32(u32(ids.length));
|
|
1022
|
-
for (let i: i32 = 0; i < ids.length; i++) {
|
|
1023
|
-
calldata.writeU256(ids[i]);
|
|
1024
|
-
}
|
|
1025
|
-
calldata.writeU32(u32(amounts.length));
|
|
1026
|
-
for (let i: i32 = 0; i < amounts.length; i++) {
|
|
1027
|
-
calldata.writeU256(amounts[i]);
|
|
1028
|
-
}
|
|
1029
|
-
calldata.writeBytesWithLength(data);
|
|
1030
|
-
|
|
1031
|
-
const response = Blockchain.call(to, calldata);
|
|
1032
|
-
if (response.data.byteLength < SELECTOR_BYTE_LENGTH) {
|
|
1033
|
-
throw new Revert('Transfer to non-OP1155Receiver implementer');
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
const retVal = response.data.readSelector();
|
|
1037
|
-
if (retVal !== ON_OP1155_BATCH_RECEIVED_MAGIC) {
|
|
1038
|
-
throw new Revert('OP1155Receiver rejected tokens');
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
}
|