-
Notifications
You must be signed in to change notification settings - Fork 0
/
PhysicalAssetTokenization.sol
418 lines (331 loc) · 15.9 KB
/
PhysicalAssetTokenization.sol
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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "./PAT_Roles.sol";
import "./PAT_Feedback.sol";
import "./PAT_Signature.sol";
// A contract for tokenizing physical tokens with three roles and feedback management
contract PhysicalAssetTokenization is ReentrancyGuard, PAT_Roles, PAT_Feedback, PAT_Signature {
// Global variables
uint256 internal maxSelectionTime = 1 *1 days;
uint256 internal maxActivationTime = 10*1 days;
uint256 internal maxRedemptionTime = 10*1 days;
// A struct to store the details of each token
struct Token {
string name; // The name of the token
string description; // A brief description of the token
uint256 initialValue; // The value of the token in wei at creation time
uint256 WTquote; // The binded amout for WT service
uint256 timeValidity; // This number define the custodial time service that start at the activation
uint256 state; // The status tracking of the token: 0-Unactive; 1-Activated; 2-OnRedemption; 3-Burned
address originator; // The address of the Vendor who started the tokenization process
address warehouse; // The address of the WarehouseTokenizator who custodies the token
address owner; // The current owner of the Token
}
// A mapping from token struct to current owner address
mapping (uint256 => address) public tokenOwner;
// A mapping to link tokenId to the token creation block
mapping(uint256 => uint256) public requestTime;
// A mapping from token ID to token struct
mapping (uint256 => Token) public tokens;
// A mapping from address to an array of ratings
mapping (address => uint256[]) public ratings;
// A mapping from token ID to fixed selling price
mapping (uint256 => uint256) public tokenSellingPrice;
// A mapping to store the nonce to validate the proof of delivery at redemption phase
mapping (uint256 => bytes32) private redemptionNonce;
// A counter for generating token IDs
uint256 public tokenCounter;
// An event to emit when an token is created by a vendor
event TokenCreated(uint256 indexed tokenId, string name, string description, uint256 value, address owner);
event WTselected(uint256 _tokenID, address _WTaddress, uint256 _WTquote);
// An event to emit when a token is activated by a WarehouseTokenizator before activation time limit
event TokenActivated(uint256 indexed tokenId, address WarehouseTokenizator, address owner);
// An evento to emit when a token request is aborder prior of activation and after activation time limit
event requestAborted(uint256 indexed tokenId, address originator, uint256 refundQuote);
// An event to emit when a token is transferred by a trader
event TokenTransferred(uint256 indexed tokenId, address from, address to);
event RedemptionRequested(uint256 indexed _tokenId, address warehouse);
// An event to emit when an token is released by a WarehouseTokenizator
event TokenReleased(uint256 indexed tokenId, address warehouse);
// An event to emit when a token is burned by a trader
event TokenBurned(uint256 indexed tokenId);
// An event to emit when the selling price is set for a token
event TokenPriceSet(uint256 indexed tokenId, uint256 sellingPrice);
// A modifier to check if the caller is the owner of a tokenId
modifier onlyTokenOwner(uint256 _tokenId) {
require(tokens[_tokenId].owner == msg.sender, "Caller is NOT the owner of this tokenId");
_;
}
// A modifier to check if the caller is the warehouse of a token
modifier onlyTokenWarehouse(uint256 _tokenId) {
require(tokens[_tokenId].warehouse == msg.sender, "Only the token warehouse can call this function");
_;
}
// A modifier to check if an token is active
modifier onlyActiveToken(uint256 _tokenId) {
require(
tokens[_tokenId].state == 1
&&
tokens[_tokenId].timeValidity > block.timestamp,
"This token is NOT active");
_;
}
// A modifier to check if an token is NOT active
modifier onlyUnactiveToken(uint256 _tokenId) {
require(tokens[_tokenId].state != 1, "This token is active");
_;
}
constructor () {
tokenCounter=0;
}
// PHASE-1: Tokenization
// A function to create a new token by a vendor
function createToken(
string memory _name,
string memory _description,
uint256 _value,
uint256 _timeValidity)
public
onlyVendor()
returns (uint256)
{
require(_timeValidity > 1, "At least 1 day of validity");
// Increment the token counter
tokenCounter++;
// Create a new token struct with the vendor as the owner and no warehouse assigned yet
Token memory newToken = Token({
name: _name,
description: _description,
initialValue: _value,
timeValidity: _timeValidity,
WTquote: 0,
originator: msg.sender,
warehouse: address(0),
owner: msg.sender,
state: 0
});
// Store the token in the mapping
tokens[tokenCounter] = newToken;
requestTime[tokenCounter]= block.timestamp;
// Emit an event
emit TokenCreated(tokenCounter, _name, _description, _value, msg.sender);
// Return the token ID
return tokenCounter;
}
function WTselection (
uint256 _tokenId,
bytes32 _messageHash,
bytes memory _signature,
address _WTaddress
) external payable
onlyUnactiveToken(_tokenId)
{
require(msg.value > 0, "Zero Ether is not allowed");
require(tokens[_tokenId].state == 0, "Token is not waiting for activation");
require(tokens[_tokenId].originator == msg.sender, "You are not the originator");
require(warehouseTokenizators[_WTaddress].active == true, "Not a valid warehouseTokenizator");
//require(_messageHash == getEIP191SignedHash(_tokenId, msg.value), "Invalid message hash");
require(_messageHash == getMessageHash(_tokenId, msg.value), "Invalid message hash");
//require(verifySignature(_messageHash, _signature, _WTaddress), "Invalid signature");
require(verify(_WTaddress, _tokenId, msg.value, _signature), "Invalid signature");
require(block.timestamp < (requestTime[_tokenId] + maxSelectionTime));
// Reset requestTime with the current bloc.timestamp as start reference for activation time
requestTime[_tokenId]=block.timestamp;
// Set Warehouse and WTquote to this _tokenId
tokens[_tokenId].warehouse = _WTaddress;
tokens[_tokenId].WTquote = msg.value;
// Emit WT seleted event to notify wich WT is binded and unbind the other WT
emit WTselected(_tokenId, _WTaddress, msg.value);
}
// A function to activate an existing token by a WarehouseTokenizator
// this function must be called by WT when pysical asset is received and checked
function activateToken(
uint256 _tokenId
) external
onlyWarehouseTokenizator()
onlyUnactiveToken(_tokenId)
{
require(tokens[_tokenId].warehouse == msg.sender, "You are not the selected WarehouseTokenizator for this tokenId");
require(block.timestamp < requestTime[_tokenId]+maxActivationTime, "You are out of time for activation");
// Make the token active
tokens[_tokenId].owner = tokens[_tokenId].originator;
tokens[_tokenId].timeValidity = block.timestamp + (tokens[_tokenId].timeValidity * 1 days); // Days of validity
tokens[_tokenId].state = 1;
// set initial value as selling price
setTokenSellingPrice(_tokenId, tokens[_tokenId].initialValue);
// set positive feedback to the originator
_setPositiveFeedback(_tokenId, tokens[_tokenId].originator);
// Emit the token activation event
emit TokenActivated(_tokenId, tokens[_tokenId].warehouse, tokens[_tokenId].owner);
}
function abortRequest(
uint256 _tokenId
) external
onlyUnactiveToken(_tokenId)
{
require(msg.sender == tokens[_tokenId].originator, "You not the originator");
require(tokens[_tokenId].WTquote > 0, "Service quote must be greater than zero");
require(block.timestamp > requestTime[_tokenId]+maxActivationTime, "You are still in of time for activation");
// Reset warehouse address
tokens[_tokenId].warehouse = address(0);
// Store the quote amount in a variable
uint256 quoteAmount = tokens[_tokenId].WTquote;
// Reset the quote amount to zero to prevent re-entry attacks
tokens[_tokenId].WTquote = 0;
negativeFeedback[tokens[_tokenId].originator].push(_tokenId);
// Transfer the quote amount to the originator (msg.sender)
payable(msg.sender).transfer(quoteAmount);
// Emit an event to alert that _tokenId request has been aborted
emit requestAborted(_tokenId, msg.sender, quoteAmount);
}
// PHASE-2 Trading
// A function for the token owner to set a fixed selling price for their token
function setTokenSellingPrice(
uint256 _tokenId,
uint256 _sellingPrice
) public
onlyTokenOwner(_tokenId)
onlyActiveToken(_tokenId)
{
// Set the selling price for the token
tokenSellingPrice[_tokenId] = _sellingPrice;
// Emit an event
emit TokenPriceSet(_tokenId, _sellingPrice);
}
// A function for a buyer to purchase a token at the fixed selling price
function purchaseToken(
uint256 _tokenId
) external payable nonReentrant
onlyActiveToken(_tokenId)
{
// Check if the sent value matches the selling price
require(msg.value == tokenSellingPrice[_tokenId], "Incorrect payment amount.");
address _previousOwner = tokens[_tokenId].owner;
// Transfer ownership of the token to the buyer
tokens[_tokenId].owner = msg.sender;
// Emit an event
emit TokenTransferred(_tokenId, _previousOwner, tokens[_tokenId].owner);
// Release the payment to the token owner as last operation
payable(_previousOwner).transfer(msg.value);
}
// PHASE-3 Redemption
// Redemption request
function redemptionRequest(
uint256 _tokenId
) external
onlyTokenOwner(_tokenId)
onlyActiveToken(_tokenId)
returns (bytes32 nonce)
{
//tokens[_tokenId].owner = address(0);
tokens[_tokenId].state = 2;
// reset redemption countdown
requestTime[_tokenId]=block.timestamp;
// redemption nonce generation
redemptionNonce[_tokenId] = generateRandomNonce(_tokenId);
emit RedemptionRequested(_tokenId, tokens[_tokenId].warehouse);
return redemptionNonce[_tokenId];
}
// A function to burn a token by warehouse in case of proof of delivery
function burnToken(
uint256 _tokenId,
bytes32 _messageHash,
bytes memory _signature
) public nonReentrant
{
// This action is only available when redemption has requested and warehouse provides proof of delivery
//require(tokens[_tokenId].owner == address(0));
require(tokens[_tokenId].state == 2, "Not in redemption state");
require(tokens[_tokenId].warehouse == msg.sender, "Only selected WT can burn token");
// Proof of delivery is a mesage signed by token pretender that contains redemptionNonce
require(_messageHash == getMessageHash(_tokenId, getRedemptionNonce(_tokenId)), "Invalid message hash");
require(verify(tokens[_tokenId].owner, _messageHash, _signature), "Invalid signature");
tokens[_tokenId].owner = address(0);
// Set token state as burned
tokens[_tokenId].state = 3;
// Release feedbacks
setPositiveFeedback(_tokenId);
// Release the payment
withdrawQuote(_tokenId);
// Emit the release event
emit TokenReleased(_tokenId, tokens[_tokenId].warehouse);
}
// UTILITY FUNCTIONS
// Function to withdraw the quote amount associated with a token
function withdrawQuote(
uint256 _tokenId
) internal
onlyUnactiveToken(_tokenId)
{
// Ensure that the token has a WarehouseTokenizator and a quote amount
require(tokens[_tokenId].warehouse != address(0), "No WarehouseTokenizator for this token");
require(tokens[_tokenId].WTquote > 0, "No amount to withdrawal");
require(msg.sender == tokens[_tokenId].warehouse || msg.sender == tokens[_tokenId].warehouse, "You are not allowed to do this");
// Store the quote amount in a variable
uint256 quoteAmount = tokens[_tokenId].WTquote;
// Reset the quote amount to zero to prevent re-entry attacks
tokens[_tokenId].WTquote = 0;
// Burn the token
tokens[_tokenId].state = 3;
// Transfer the quote amount to the token owner (msg.sender)
payable(msg.sender).transfer(quoteAmount);
}
// External since negative feedback are manual only
function setNegativeFeedbackByOriginator(
uint256 _tokenId
) external
returns (bool success)
{
require(msg.sender == tokens[_tokenId].originator, "Only originator can do this");
require(tokens[_tokenId].WTquote > 0, "Service quote must be >0");
require(tokens[_tokenId].state == 0, "Token has been activated");
require(block.timestamp > requestTime[_tokenId]+maxActivationTime, "Still in time for activation");
// ATTENZIONE:Bisogna distinguere questo feedback da quello di un asset non consegnato
_setNegativeFeedback(_tokenId, tokens[_tokenId].warehouse);
// Disable the token
tokens[_tokenId].state = 3;
withdrawQuote(_tokenId);
return true;
}
// External since negative feedback are manual only
function setNegativeFeedbackByOwner(
uint256 _tokenId
) external
returns (bool success)
{
require(tokens[_tokenId].owner == msg.sender, "Only token owner can do this");
require(tokens[_tokenId].state == 2, "Not in redemption state");
require(block.timestamp > requestTime[_tokenId]+maxRedemptionTime, "Redemption time has not expired");
// Disable the token
tokens[_tokenId].state = 3;
_setNegativeFeedback(_tokenId, tokens[_tokenId].warehouse);
withdrawQuote(_tokenId);
return true;
}
// Internal since positive feedback are automated only
function setPositiveFeedback(
uint256 _tokenId
) internal
returns (bool success)
{
require(tokens[_tokenId].warehouse == msg.sender, "Only selected warehouse can do this");
require(tokens[_tokenId].state==3, "Token has not been burned");
return _setPositiveFeedback(_tokenId, msg.sender);
}
// Fallback function to avoid directly receiving payment, so when payable function are not called
fallback()
external
{
revert("This contract does not accept Ether transactions.");
}
function getRedemptionNonce(
uint256 _tokenId
) public view
returns (bytes32 _redemptionNonce)
{
require(tokens[_tokenId].state == 2, "Not in redemption state");
require(msg.sender == tokens[_tokenId].owner || msg.sender == tokens[_tokenId].warehouse, "Not allowed to get redemption nonce");
return redemptionNonce[_tokenId];
}
}