/ contracts / Discover.sol
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  }