-
Notifications
You must be signed in to change notification settings - Fork 2
/
Passport.sol
247 lines (205 loc) · 9.03 KB
/
Passport.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
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.10;
import {ERC20} from "@rari-capital/solmate/src/tokens/ERC20.sol";
import {ERC721, ERC721TokenReceiver} from "@rari-capital/solmate/src/tokens/ERC721.sol";
import {SafeTransferLib} from "@rari-capital/solmate/src/utils/SafeTransferLib.sol";
import {Controlled} from "../utils/Controlled.sol";
import {Renderer} from "./render/Renderer.sol";
/// @notice Non-fungible, limited-transferable token that grants citizen status in Nation3.
/// @author Nation3 (https://github.com/nation3/app/blob/main/contracts/contracts/passport/Passport.sol).
/// @dev Most ERC721 operations are restricted to controller contract.
/// @dev Is modified from the EIP-721 because of the lack of enough integration of the EIP-4973 at the moment of development.
/// @dev Token metadata is renderer on-chain through an external contract.
contract Passport is ERC721, Controlled {
/*///////////////////////////////////////////////////////////////
LIBRARIES
//////////////////////////////////////////////////////////////*/
using SafeTransferLib for ERC20;
/*///////////////////////////////////////////////////////////////
ERRORS
//////////////////////////////////////////////////////////////*/
error NotMinted();
error NotAuthorized();
error InvalidFrom();
error NotSafeRecipient();
/*///////////////////////////////////////////////////////////////
STORAGE
//////////////////////////////////////////////////////////////*/
// @notice On-chain metadata renderer.
Renderer public renderer;
/// @dev Tracks the number of tokens minted & not burned.
uint256 internal _supply;
/// @dev Tracks the next id to mint.
uint256 internal _idTracker;
// @dev Timestamp of each token mint.
mapping(uint256 => uint256) internal _timestampOf;
// @dev Authorized address to sign messages in behalf of the passport holder, it can be different from the owner.
// @dev Could be used for IRL events authentication.
mapping(uint256 => address) internal _signerOf;
/*///////////////////////////////////////////////////////////////
VIEWS
//////////////////////////////////////////////////////////////*/
/// @notice Returns total number of tokens in supply.
function totalSupply() external view virtual returns (uint256) {
return _supply;
}
/// @notice Gets next id to mint.
function getNextId() external view virtual returns (uint256) {
return _idTracker;
}
/// @notice Returns the timestamp of the mint of a token.
/// @param id Token to retrieve timestamp from.
function timestampOf(uint256 id) public view virtual returns (uint256) {
if (_ownerOf[id] == address(0)) revert NotMinted();
return _timestampOf[id];
}
/// @notice Returns the authorized signer of a token.
/// @param id Token to retrieve signer from.
function signerOf(uint256 id) external view virtual returns (address) {
if (_ownerOf[id] == address(0)) revert NotMinted();
return _signerOf[id];
}
/// @notice Get encoded metadata from renderer.
/// @param id Token to retrieve metadata from.
function tokenURI(uint256 id) public view override returns (string memory) {
return renderer.render(id, ownerOf(id), timestampOf(id));
}
/*///////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
/// @dev Sets name & symbol.
constructor(string memory _name, string memory _symbol) ERC721(_name, _symbol) {}
/*///////////////////////////////////////////////////////////////
USER ACTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Allows the owner of a passport to update the signer.
/// @param id Token to update the signer.
/// @param signer Address of the new signer account.
function setSigner(uint256 id, address signer) external virtual {
if (_ownerOf[id] != msg.sender) revert NotAuthorized();
_signerOf[id] = signer;
}
/*///////////////////////////////////////////////////////////////
CONTROLLED ACTIONS
//////////////////////////////////////////////////////////////*/
/// @notice ERC721 method to set allowance. Only allowed to controller.
/// @dev Prevent approvals on marketplaces & other contracts.
function approve(address spender, uint256 id) public override onlyController {
getApproved[id] = spender;
emit Approval(_ownerOf[id], spender, id);
}
/// @notice ERC721 method to set allowance. Only allowed to controller.
/// @dev Prevent approvals on marketplaces & other contracts.
function setApprovalForAll(address operator, bool approved) public override onlyController {
isApprovedForAll[msg.sender][operator] = approved;
emit ApprovalForAll(msg.sender, operator, approved);
}
/// @notice Allows controller to transfer a passport (id) between two addresses.
/// @param from Current owner of the token.
/// @param to Recipient of the token.
/// @param id Token to transfer.
function transferFrom(
address from,
address to,
uint256 id
) public override onlyController {
if (from != _ownerOf[id]) revert InvalidFrom();
if (to == address(0)) revert TargetIsZeroAddress();
unchecked {
_balanceOf[from]--;
_balanceOf[to]++;
}
_ownerOf[id] = to;
_timestampOf[id] = block.timestamp;
_signerOf[id] = to;
delete getApproved[id];
emit Transfer(from, to, id);
}
/// @notice Allows controller to safe transfer a passport (id) between two address.
/// @param from Curent owner of the token.
/// @param to Recipient of the token.
/// @param id Token to transfer.
function safeTransferFrom(
address from,
address to,
uint256 id
) public override onlyController {
transferFrom(from, to, id);
if (
to.code.length != 0 &&
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, "") !=
ERC721TokenReceiver.onERC721Received.selector
) revert NotSafeRecipient();
}
/// @notice Allows controller to safe transfer a passport (id) between two address.
/// @param from Curent owner of the token.
/// @param to Recipient of the token.
/// @param id Token to transfer.
function safeTransferFrom(
address from,
address to,
uint256 id,
bytes calldata data
) public override onlyController {
transferFrom(from, to, id);
if (
to.code.length != 0 &&
ERC721TokenReceiver(to).onERC721Received(msg.sender, from, id, data) !=
ERC721TokenReceiver.onERC721Received.selector
) revert NotSafeRecipient();
}
/// @notice Mints a new passport to the recipient.
/// @param to Token recipient.
/// @dev Id is auto assigned.
function mint(address to) external virtual onlyController returns (uint256 tokenId) {
tokenId = _idTracker;
_mint(to, tokenId);
// Realistically won't overflow;
unchecked {
_timestampOf[tokenId] = block.timestamp;
_signerOf[tokenId] = to;
_idTracker++;
_supply++;
}
}
/// @notice Mints a new passport to the recipient.
/// @param to Token recipient.
/// @dev Id is auto assigned.
function safeMint(address to) external virtual onlyController returns (uint256 tokenId) {
tokenId = _idTracker;
_safeMint(to, tokenId);
// Realistically won't overflow;
unchecked {
_timestampOf[tokenId] = block.timestamp;
_signerOf[tokenId] = to;
_idTracker++;
_supply++;
}
}
/// @notice Burns the specified token.
/// @param id Token to burn.
function burn(uint256 id) external virtual onlyController {
_burn(id);
// Would have reverted before if the token wasnt minted
unchecked {
delete _timestampOf[id];
delete _signerOf[id];
_supply--;
}
}
/*///////////////////////////////////////////////////////////////
ADMIN ACTIONS
//////////////////////////////////////////////////////////////*/
/// @notice Allows the owner to update the renderer contract.
/// @param _renderer New renderer address.
function setRenderer(Renderer _renderer) external virtual onlyOwner {
renderer = _renderer;
}
/// @notice Allows the owner to withdraw any ERC20 sent to the contract.
/// @param token Token to withdraw.
/// @param to Recipient address of the tokens.
function recoverTokens(ERC20 token, address to) external virtual onlyOwner returns (uint256 amount) {
amount = token.balanceOf(address(this));
token.safeTransfer(to, amount);
}
}