-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathHarbegerNFT.sol
221 lines (176 loc) · 7.76 KB
/
HarbegerNFT.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
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.3;
import "./ERC721HarbegerEnumerable.sol";
// When running your contracts and tests on Hardhat Network
// you can print logging messages and contract variables calling console.log() from your Solidity code.
// https://hardhat.org/tutorial/debugging-with-hardhat-network
import "hardhat/console.sol";
contract HarbegerNFT is ERC721ERC721HarbegerEnumerable {
event Minted(uint indexed tokenId, address from);
event Deposited(uint indexed tokenId, uint value, address from);
event PriceUpdated(uint indexed tokenId, uint newPrice, address from);
event Collected(uint indexed tokenId, uint taxAmount, address from);
event Bought(uint indexed tokenId, uint paidAmount, uint latestPrice, address from);
event Refunded(uint indexed tokenId, uint refund, address to);
event Reclaimed(uint indexed tokenId, address from);
uint32 private constant TAX_DENOMINATOR = 10000;
// TAX_NUMERATOR and TAX_INTERVAL should be configurable values
uint32 private constant TAX_NUMERATOR = 250; // 2.5%
uint32 private constant TAX_INTERVAL = 1 seconds;
// Mapping from creator to list of token IDs
mapping(address => uint[]) private _createdTokens;
// Who receives taxes from taxes
mapping(uint => address) private _tokenCreator;
// Harberger Tax variables
// TODO: make into struct?
mapping(uint => uint) public taxes;
mapping(uint => uint) public prices;
mapping(uint => uint) public paidThru;
constructor () ERC721ERC721HarbegerEnumerable("Harbeger NFT", "HARB") {}
/**
* @dev Throws if called by any account other than the owner of a tokenId.
*/
modifier onlyOwnerOf(uint tokenId) {
require(exists(tokenId), "Token doesn't exist.");
require(ownerOf(tokenId) == msg.sender, "Caller is not the token owner");
_;
}
/**
* @dev Function to mint tokens. From ERC721MetadataMintable
* @param tokenId The token id to mint.
* @param tokenURI The token URI of the minted token.
* @return A boolean that indicates if the operation was successful.
*/
function mint(uint tokenId, string memory tokenURI) public returns (bool) {
address creator = msg.sender;
_mint(creator, tokenId); // Initially, the creator owns the token but is 'in recovery'
_setTokenURI(tokenId, tokenURI); //tokenURI is the Tier
_createdTokens[creator].push(tokenId); // Track a Creator's tokens
_tokenCreator[tokenId] = creator;
// HTax parameters
prices[tokenId] = 0;
taxes[tokenId] = 0;
paidThru[tokenId] = block.timestamp;
emit Minted(tokenId, creator);
return true;
}
// Buys a Collectible
function buy(uint tokenId, uint newPrice) public payable { // TODO: must be non-reentrant
uint paidAmount = msg.value;
collect(tokenId); // Collect taxes
reclaim(tokenId); // Reclaim if possible to lower price to 0
uint latestPrice = prices[tokenId];
require(paidAmount >= latestPrice, 'Insufficient amount paid.');
address previousOwner = _tokenOwner[tokenId];
require(msg.sender != previousOwner, 'You are already the owner.');
address payable beneficiary = payable(address(uint160(previousOwner)));
uint excessTaxes = taxes[tokenId]; // Refund excess taxes to previous owner
// Change owner
address newOwner = msg.sender;
uint excessPaidAmount = paidAmount - latestPrice; // TODO: use SafeMath.sub()
taxes[tokenId] = excessPaidAmount; // Excess paid is deposited as taxes
paidThru[tokenId] = block.timestamp;
_changeOwner(tokenId, previousOwner, newOwner);
// Interchangeable newPrice and paidAmount
prices[tokenId] = newPrice;
emit Bought(tokenId, paidAmount, latestPrice, newOwner);
uint refund = excessTaxes + latestPrice;
emit Refunded(tokenId, refund, beneficiary);
// TOFIX: fails because insufficient balance
// address(this).balance = 1860
beneficiary.transfer(refund); // Transfer remaining taxes + profit to previous owner
}
// Collect taxes
// Forbid collection of collectibles with zero balances
function collect(uint tokenId) public {
uint owed = taxOwed(tokenId);
address payable beneficiary = payable(address(uint160(creatorOf(tokenId))));
uint paid = taxes[tokenId];
if (owed > paid) { // insufficient tax deposited
taxes[tokenId] = 0;
// Represent amount of taxes paid as a time
// As each tax payment corresponds to tax time interval
paidThru[tokenId] += (block.timestamp - paidThru[tokenId]) * paid / owed;
emit Collected(tokenId, paid, msg.sender);
beneficiary.transfer(paid);
} else { // enough tax deposited
taxes[tokenId] -= owed;
paidThru[tokenId] = block.timestamp;
emit Collected(tokenId, owed, msg.sender);
beneficiary.transfer(owed); // Pay creator
}
}
// Reclaim tokens if taxes are underpaid
function reclaim(uint tokenId) public {
if(canReclaim(tokenId)) {
_changeOwner(tokenId, _tokenOwner[tokenId], address(0)); // Unowned
// Reset price, tax balance, and taxes owed
prices[tokenId] = 0;
taxes[tokenId] = 0;
paidThru[tokenId] = block.timestamp;
emit Reclaimed(tokenId, msg.sender);
}
}
// Pay taxes
function deposit(uint tokenId) public payable onlyOwnerOf(tokenId) {
uint amount = msg.value;
require(taxOwed(tokenId) <= amount, 'Must deposit enough taxes to cover unpaid.');
taxes[tokenId] += amount;
emit Deposited(tokenId, amount, msg.sender);
}
// Update token price
function setPrice(uint tokenId, uint newPrice) public onlyOwnerOf(tokenId) {
collect(tokenId);
require(!canReclaim(tokenId), 'Must fully pay taxes first before changing price.');
prices[tokenId] = newPrice;
emit PriceUpdated(tokenId, newPrice, msg.sender);
}
function taxOwed(uint tokenId) public view returns (uint) {
uint tokenPrice = prices[tokenId];
return tokenPrice * TAX_NUMERATOR / TAX_DENOMINATOR
* (block.timestamp - paidThru[tokenId]) / TAX_INTERVAL;
}
function exists(uint tokenId) public view returns (bool) {
return _exists(tokenId);
}
function tokensOfCreator(address creator) public view returns (uint[] memory) {
return _tokensOfCreator(creator);
}
function tokensOfOwner(address owner) public view returns (uint[] memory) {
return _tokensOfOwner(owner);
}
function info(uint tokenId) public view returns (address, address, uint, uint, bool) {
address creator = creatorOf(tokenId);
address owner = ownerOf(tokenId);
uint taxBalance = taxes[tokenId];
uint price = prices[tokenId];
bool _canReclaim = canReclaim(tokenId);
return (creator, owner, taxBalance, price, _canReclaim);
}
/**
* @dev Gets the creator of the specified token ID.
* @param tokenId uint ID of the token to query the creator of
* @return address currently marked as the creator of the given token ID
*/
function creatorOf(uint tokenId) public view returns (address) {
address creator = _tokenCreator[tokenId];
require(creator != address(0), "ERC721: creator query for nonexistent token");
return creator;
}
function canReclaim(uint tokenId) public view returns (bool) {
return (ownerOf(tokenId) != address(0) && prices[tokenId] > 0 && taxOwed(tokenId) > taxes[tokenId]);
}
function _changeOwner(uint tokenId, address oldOwner, address newOwner) internal {
_removeTokenFromOwnerEnumeration(oldOwner, tokenId);
_tokenOwner[tokenId] = newOwner;
_addTokenToOwnerEnumeration(newOwner, tokenId);
}
/**
* @dev Gets the list of token IDs of the requested creator.
* @param creator address who created the tokens
* @return uint[] List of token IDs owned by the requested address
*/
function _tokensOfCreator(address creator) internal view returns (uint[] storage) {
return _createdTokens[creator];
}
}