Discover.sol
1 pragma solidity ^0.5.2; 2 3 import "./token/MiniMeTokenInterface.sol"; 4 import "./token/ApproveAndCallFallBack.sol"; 5 import "./utils/SafeMath.sol"; 6 import "./utils/BancorFormula.sol"; 7 8 9 contract Discover is ApproveAndCallFallBack, BancorFormula { 10 using SafeMath for uint; 11 12 // Could be any MiniMe token 13 MiniMeTokenInterface SNT; 14 15 // Total SNT in circulation 16 uint public total; 17 18 // Parameter to calculate Max SNT any one DApp can stake 19 uint public ceiling; 20 21 // The max amount of tokens it is possible to stake, as a percentage of the total in circulation 22 uint public max; 23 24 // Decimal precision for this contract 25 uint public decimals; 26 27 // Prevents overflows in votesMinted 28 uint public safeMax; 29 30 // Whether we need more than an id param to identify arbitrary data must still be discussed. 31 struct Data { 32 address developer; 33 bytes32 id; 34 bytes32 metadata; 35 uint balance; 36 uint rate; 37 uint available; 38 uint votesMinted; 39 uint votesCast; 40 uint effectiveBalance; 41 } 42 43 Data[] public dapps; 44 mapping(bytes32 => uint) public id2index; 45 mapping(bytes32 => bool) existingIDs; 46 47 event DAppCreated(bytes32 indexed id, uint newEffectiveBalance); 48 event Upvote(bytes32 indexed id, uint newEffectiveBalance); 49 event Downvote(bytes32 indexed id, uint newEffectiveBalance); 50 event Withdraw(bytes32 indexed id, uint newEffectiveBalance); 51 event MetadataUpdated(bytes32 indexed id); 52 53 constructor(MiniMeTokenInterface _SNT) public { 54 SNT = _SNT; 55 56 total = 3470483788; 57 58 ceiling = 588; // See here for more: https://observablehq.com/@andytudhope/dapp-store-snt-curation-mechanism 59 60 decimals = 1000000; // 4 decimal points for %, 2 because we only use 1/100th of total in circulation 61 62 max = total.mul(ceiling).div(decimals); 63 64 safeMax = uint(77).mul(max).div(100); // Limited by accuracy of BancorFormula 65 } 66 67 /** 68 * @dev Anyone can create a DApp (i.e an arb piece of data this contract happens to care about). 69 * @param _id bytes32 unique identifier. 70 * @param _amount of tokens to stake on initial ranking. 71 * @param _metadata metadata hex string 72 */ 73 function createDApp(bytes32 _id, uint _amount, bytes32 _metadata) external { 74 _createDApp( 75 msg.sender, 76 _id, 77 _amount, 78 _metadata); 79 } 80 81 /** 82 * @dev Sends SNT directly to the contract, not the developer. This gets added to the DApp's balance, no curve required. 83 * @param _id bytes32 unique identifier. 84 * @param _amount of tokens to stake on DApp's ranking. Used for upvoting + staking more. 85 */ 86 function upvote(bytes32 _id, uint _amount) external { 87 _upvote(msg.sender, _id, _amount); 88 } 89 90 /** 91 * @dev Sends SNT to the developer and lowers the DApp's effective balance by 1% 92 * @param _id bytes32 unique identifier. 93 * @param _amount uint, included for approveAndCallFallBack 94 */ 95 function downvote(bytes32 _id, uint _amount) external { 96 _downvote(msg.sender, _id, _amount); 97 } 98 99 /** 100 * @dev Developers can withdraw an amount not more than what was available of the 101 SNT they originally staked minus what they have already received back in downvotes. 102 * @param _id bytes32 unique identifier. 103 * @param _amount of tokens to withdraw from DApp's overall balance. 104 */ 105 function withdraw(bytes32 _id, uint _amount) external { 106 Data storage d = _getDAppById(_id); 107 108 require(msg.sender == d.developer, "Only the developer can withdraw SNT staked on this data"); 109 require(_amount <= d.available, "You can only withdraw a percentage of the SNT staked, less what you have already received"); 110 111 uint precision; 112 uint result; 113 114 d.balance = d.balance.sub(_amount); 115 d.rate = decimals.sub(d.balance.mul(decimals).div(max)); 116 d.available = d.balance.mul(d.rate); 117 118 (result, precision) = BancorFormula.power( 119 d.available, 120 decimals, 121 uint32(decimals), 122 uint32(d.rate)); 123 124 d.votesMinted = result >> precision; 125 if (d.votesCast > d.votesMinted) { 126 d.votesCast = d.votesMinted; 127 } 128 129 uint temp1 = d.votesCast.mul(d.rate).mul(d.available); 130 uint temp2 = d.votesMinted.mul(decimals).mul(decimals); 131 uint effect = temp1.div(temp2); 132 133 d.effectiveBalance = d.balance.sub(effect); 134 135 require(SNT.transfer(d.developer, _amount), "Transfer failed"); 136 137 emit Withdraw(_id, d.effectiveBalance); 138 } 139 140 /** 141 * dev Set the content for the dapp 142 * @param _id bytes32 unique identifier. 143 * @param _metadata metadata info 144 */ 145 function setMetadata(bytes32 _id, bytes32 _metadata) external { 146 uint dappIdx = id2index[_id]; 147 Data storage d = dapps[dappIdx]; 148 require(d.developer == msg.sender, "Only the developer can update the metadata"); 149 d.metadata = _metadata; 150 emit MetadataUpdated(_id); 151 } 152 153 /** 154 * @dev Used in UI in order to fetch all dapps 155 * @return dapps count 156 */ 157 function getDAppsCount() external view returns(uint) { 158 return dapps.length; 159 } 160 161 /** 162 * @notice Support for "approveAndCall". 163 * @param _from Who approved. 164 * @param _amount Amount being approved, needs to be equal `_amount` or `cost`. 165 * @param _token Token being approved, needs to be `SNT`. 166 * @param _data Abi encoded data with selector of `register(bytes32,address,bytes32,bytes32)`. 167 */ 168 function receiveApproval( 169 address _from, 170 uint256 _amount, 171 address _token, 172 bytes calldata _data 173 ) 174 external 175 { 176 require(_token == address(SNT), "Wrong token"); 177 require(_token == address(msg.sender), "Wrong account"); 178 require(_data.length <= 196, "Incorrect data"); 179 180 bytes4 sig; 181 bytes32 id; 182 uint256 amount; 183 bytes32 metadata; 184 185 (sig, id, amount, metadata) = abiDecodeRegister(_data); 186 require(_amount == amount, "Wrong amount"); 187 188 if (sig == bytes4(0x7e38d973)) { 189 _createDApp( 190 _from, 191 id, 192 amount, 193 metadata); 194 } else if (sig == bytes4(0xac769090)) { 195 _downvote(_from, id, amount); 196 } else if (sig == bytes4(0x2b3df690)) { 197 _upvote(_from, id, amount); 198 } else { 199 revert("Wrong method selector"); 200 } 201 } 202 203 /** 204 * @dev Used in UI to display effect on ranking of user's donation 205 * @param _id bytes32 unique identifier. 206 * @param _amount of tokens to stake/"donate" to this DApp's ranking. 207 * @return effect of donation on DApp's effectiveBalance 208 */ 209 function upvoteEffect(bytes32 _id, uint _amount) external view returns(uint effect) { 210 Data memory d = _getDAppById(_id); 211 require(d.balance.add(_amount) <= safeMax, "You cannot upvote by this much, try with a lower amount"); 212 213 // Special case - no downvotes yet cast 214 if (d.votesCast == 0) { 215 return _amount; 216 } 217 218 uint precision; 219 uint result; 220 221 uint mBalance = d.balance.add(_amount); 222 uint mRate = decimals.sub(mBalance.mul(decimals).div(max)); 223 uint mAvailable = mBalance.mul(mRate); 224 225 (result, precision) = BancorFormula.power( 226 mAvailable, 227 decimals, 228 uint32(decimals), 229 uint32(mRate)); 230 231 uint mVMinted = result >> precision; 232 233 uint temp1 = d.votesCast.mul(mRate).mul(mAvailable); 234 uint temp2 = mVMinted.mul(decimals).mul(decimals); 235 uint mEffect = temp1.div(temp2); 236 237 uint mEBalance = mBalance.sub(mEffect); 238 239 return (mEBalance.sub(d.effectiveBalance)); 240 } 241 242 /** 243 * @dev Downvotes always remove 1% of the current ranking. 244 * @param _id bytes32 unique identifier. 245 * @return balance_down_by, votes_required, cost 246 */ 247 function downvoteCost(bytes32 _id) public view returns(uint b, uint vR, uint c) { 248 Data memory d = _getDAppById(_id); 249 return _downvoteCost(d); 250 } 251 252 function _createDApp( 253 address _from, 254 bytes32 _id, 255 uint _amount, 256 bytes32 _metadata 257 ) 258 internal 259 { 260 require(!existingIDs[_id], "You must submit a unique ID"); 261 262 require(_amount > 0, "You must spend some SNT to submit a ranking in order to avoid spam"); 263 require (_amount <= safeMax, "You cannot stake more SNT than the ceiling dictates"); 264 265 uint dappIdx = dapps.length; 266 267 dapps.length++; 268 269 Data storage d = dapps[dappIdx]; 270 d.developer = _from; 271 d.id = _id; 272 d.metadata = _metadata; 273 274 uint precision; 275 uint result; 276 277 d.balance = _amount; 278 d.rate = decimals.sub((d.balance).mul(decimals).div(max)); 279 d.available = d.balance.mul(d.rate); 280 281 (result, precision) = BancorFormula.power( 282 d.available, 283 decimals, 284 uint32(decimals), 285 uint32(d.rate)); 286 287 d.votesMinted = result >> precision; 288 d.votesCast = 0; 289 d.effectiveBalance = _amount; 290 291 id2index[_id] = dappIdx; 292 existingIDs[_id] = true; 293 294 require(SNT.allowance(_from, address(this)) >= _amount, "Not enough SNT allowance"); 295 require(SNT.transferFrom(_from, address(this), _amount), "Transfer failed"); 296 297 emit DAppCreated(_id, d.effectiveBalance); 298 } 299 300 function _upvote(address _from, bytes32 _id, uint _amount) internal { 301 require(_amount > 0, "You must send some SNT in order to upvote"); 302 303 Data storage d = _getDAppById(_id); 304 305 require(d.balance.add(_amount) <= safeMax, "You cannot upvote by this much, try with a lower amount"); 306 307 uint precision; 308 uint result; 309 310 d.balance = d.balance.add(_amount); 311 d.rate = decimals.sub((d.balance).mul(decimals).div(max)); 312 d.available = d.balance.mul(d.rate); 313 314 (result, precision) = BancorFormula.power( 315 d.available, 316 decimals, 317 uint32(decimals), 318 uint32(d.rate)); 319 320 d.votesMinted = result >> precision; 321 322 uint temp1 = d.votesCast.mul(d.rate).mul(d.available); 323 uint temp2 = d.votesMinted.mul(decimals).mul(decimals); 324 uint effect = temp1.div(temp2); 325 326 d.effectiveBalance = d.balance.sub(effect); 327 328 require(SNT.allowance(_from, address(this)) >= _amount, "Not enough SNT allowance"); 329 require(SNT.transferFrom(_from, address(this), _amount), "Transfer failed"); 330 331 emit Upvote(_id, d.effectiveBalance); 332 } 333 334 function _downvote(address _from, bytes32 _id, uint _amount) internal { 335 Data storage d = _getDAppById(_id); 336 (uint b, uint vR, uint c) = _downvoteCost(d); 337 338 require(_amount == c, "Incorrect amount: valid iff effect on ranking is 1%"); 339 340 d.available = d.available.sub(_amount); 341 d.votesCast = d.votesCast.add(vR); 342 d.effectiveBalance = d.effectiveBalance.sub(b); 343 344 require(SNT.allowance(_from, address(this)) >= _amount, "Not enough SNT allowance"); 345 require(SNT.transferFrom(_from, address(this), _amount), "Transfer failed"); 346 require(SNT.transfer(d.developer, _amount), "Transfer failed"); 347 348 emit Downvote(_id, d.effectiveBalance); 349 } 350 351 function _downvoteCost(Data memory d) internal view returns(uint b, uint vR, uint c) { 352 uint balanceDownBy = (d.effectiveBalance.div(100)); 353 uint votesRequired = (balanceDownBy.mul(d.votesMinted).mul(d.rate)).div(d.available); 354 uint votesAvailable = d.votesMinted.sub(d.votesCast).sub(votesRequired); 355 uint temp = (d.available.div(votesAvailable)).mul(votesRequired); 356 uint cost = temp.div(decimals); 357 return (balanceDownBy, votesRequired, cost); 358 } 359 360 /** 361 * @dev Used internally in order to get a dapp while checking if it exists 362 * @return existing dapp 363 */ 364 function _getDAppById(bytes32 _id) internal view returns(Data storage d) { 365 uint dappIdx = id2index[_id]; 366 Data memory d = dapps[dappIdx]; 367 require(d.id == _id, "Error fetching correct data"); 368 369 return dapps[dappIdx]; 370 } 371 372 373 /** 374 * @dev Decodes abi encoded data with selector for "functionName(bytes32,uint256)". 375 * @param _data Abi encoded data. 376 * @return Decoded registry call. 377 */ 378 function abiDecodeRegister( 379 bytes memory _data 380 ) 381 private 382 returns( 383 bytes4 sig, 384 bytes32 id, 385 uint256 amount, 386 bytes32 metadata 387 ) 388 { 389 assembly { 390 sig := mload(add(_data, add(0x20, 0))) 391 id := mload(add(_data, 36)) 392 amount := mload(add(_data, 68)) 393 metadata := mload(add(_data, 100)) 394 } 395 } 396 }