@@ -154,15 +154,15 @@ contract ColonyNetwork is BasicMetaTransaction, ColonyNetworkStorage, Multicall
154154 skillCount += 1 ;
155155 addSkillToChainTree (_parentSkillId, skillCount);
156156
157- if (! isMiningChain ()) {
158- bridgeSkill (skillCount);
159- }
157+ bridgeSkillIfNotMiningChain (skillCount);
160158
161159 emit SkillAdded (skillCount, _parentSkillId);
162160 return skillCount;
163161 }
164162
165- function bridgeSkill (uint256 _skillId ) public stoppable onlyNotMiningChain skillExists (_skillId) {
163+ function bridgeSkillIfNotMiningChain (uint256 _skillId ) public stoppable skillExists (_skillId) {
164+ // If we're the mining chain, we don't need to bridge
165+ if (isMiningChain ()){ return ; }
166166 // Send bridge transaction
167167 // Build the transaction we're going to send to the bridge to register the
168168 // creation of this skill on the home chain
@@ -175,7 +175,6 @@ contract ColonyNetwork is BasicMetaTransaction, ColonyNetworkStorage, Multicall
175175 bridgeData[miningBridgeAddress].skillCreationAfter
176176 );
177177
178- // TODO: If there's no contract there, I think this currently succeeds (when we wouldn't want it to)
179178 (bool success , bytes memory returnData ) = miningBridgeAddress.call (payload);
180179 require (success, "colony-network-unable-to-bridge-skill-creation " );
181180 }
@@ -237,9 +236,7 @@ contract ColonyNetwork is BasicMetaTransaction, ColonyNetworkStorage, Multicall
237236
238237 // Check skill count - if not next, then store for later.
239238 if (networkSkillCounts[bridge.chainId] + 1 == _skillId){
240- if (_parentSkillId > bridge.chainId << 128 ){
241- addSkillToChainTree (_parentSkillId, _skillId);
242- }
239+ addSkillToChainTree (_parentSkillId, _skillId);
243240 networkSkillCounts[bridge.chainId] += 1 ;
244241 emit SkillAdded (_skillId, _parentSkillId);
245242 } else if (networkSkillCounts[bridge.chainId] < _skillId){
@@ -252,11 +249,11 @@ contract ColonyNetwork is BasicMetaTransaction, ColonyNetworkStorage, Multicall
252249 return pendingSkillAdditions[_chainId][_skillCount];
253250 }
254251
255- function getBridgeSkillCounts (uint256 _chainId ) public view returns (uint256 ){
252+ function getBridgedSkillCounts (uint256 _chainId ) public view returns (uint256 ){
256253 return networkSkillCounts[_chainId];
257254 }
258255
259- function addPendingSkillFromBridge (address _bridgeAddress , uint256 _skillId ) public always onlyMiningChain () {
256+ function addBridgedPendingSkill (address _bridgeAddress , uint256 _skillId ) public always onlyMiningChain () {
260257 Bridge storage bridge = bridgeData[_bridgeAddress];
261258 require (bridge.chainId != 0 , "colony-network-not-known-bridge " );
262259
@@ -311,34 +308,106 @@ contract ColonyNetwork is BasicMetaTransaction, ColonyNetworkStorage, Multicall
311308 returns (uint256 )
312309 {
313310 skillCount += 1 ;
314- if (! isMiningChain ()){
315- bridgeSkill (skillCount);
316- }
311+ bridgeSkillIfNotMiningChain (skillCount);
317312 return skillCount;
318313 }
319314
320- function appendReputationUpdateLogFromBridge (address _colony , address _user , int _amount , uint _skillId ) public onlyMiningChain stoppable
315+ function appendReputationUpdateLogFromBridge (address _colony , address _user , int _amount , uint _skillId , uint256 _updateNumber ) public onlyMiningChain stoppable
321316 {
322317 // Require is a known bridge
323318 require (bridgeData[msgSender ()].chainId != 0 , "colony-network-not-known-bridge " );
324319
325- require (networkSkillCounts[_skillId >> 128 ] >= _skillId, "colony-network-invalid-skill-id " );
326320 require (bridgeData[msgSender ()].chainId == _skillId >> 128 , "colony-network-invalid-skill-id-for-bridge " );
327321
328- uint128 nParents = skills[_skillId].nParents;
322+ // if next expected update, add to log
323+ if (
324+ reputationUpdateCount[bridgeData[msgSender ()].chainId][_colony] + 1 == _updateNumber && // It's the next reputation update for this colony
325+ networkSkillCounts[_skillId >> 128 ] >= _skillId // Skill has been bridged
326+ ){
327+ reputationUpdateCount[bridgeData[msgSender ()].chainId][_colony] += 1 ;
328+ uint128 nParents = skills[_skillId].nParents;
329+ // We only update child skill reputation if the update is negative, otherwise just set nChildren to 0 to save gas
330+ uint128 nChildren = _amount < 0 ? skills[_skillId].nChildren : 0 ;
331+
332+ IReputationMiningCycle (inactiveReputationMiningCycle).appendReputationUpdateLog (
333+ _user,
334+ _amount,
335+ _skillId,
336+ _colony,
337+ nParents,
338+ nChildren
339+ );
340+
341+ } else {
342+ // Not next update, store for later
343+ pendingReputationUpdates[bridgeData[msgSender ()].chainId][_colony][_updateNumber] = PendingReputationUpdate (_colony, _user, _amount, _skillId, block .timestamp );
344+ }
345+ }
346+
347+ function bridgePendingReputationUpdate (address _colony , uint256 _updateNumber ) public stoppable onlyNotMiningChain {
348+ // Must be next update
349+ require (pendingReputationUpdates[getChainId ()][_colony][_updateNumber - 1 ].colony == address (0x00 ), "colony-network-not-next-pending-update " );
350+ require (pendingReputationUpdates[getChainId ()][_colony][_updateNumber].colony != address (0x00 ), "colony-network-update-does-not-exist " );
351+ PendingReputationUpdate storage pendingUpdate = pendingReputationUpdates[getChainId ()][_colony][_updateNumber];
352+
353+ require (miningBridgeAddress != address (0x0 ), "colony-network-foreign-bridge-not-set " );
354+
355+ int256 updateAmount = decayReputation (pendingUpdate.amount, pendingUpdate.timestamp);
356+
357+ // Build the transaction we're going to send to the bridge
358+ bytes memory payload = abi.encodePacked (
359+ bridgeData[miningBridgeAddress].updateLogBefore,
360+ abi.encodeWithSignature ("appendReputationUpdateLogFromBridge(address,address,int256,uint256,uint256) " , pendingUpdate.colony, pendingUpdate.user, updateAmount, pendingUpdate.skillId, _updateNumber),
361+ bridgeData[miningBridgeAddress].updateLogAfter
362+ );
363+
364+ delete pendingReputationUpdates[getChainId ()][_colony][_updateNumber];
365+
366+ (bool success , ) = miningBridgeAddress.call (payload);
367+ require (success, "colony-network-bridging-tx-unsuccessful " );
368+ }
369+
370+ function addBridgedReputationUpdate (uint256 _chainId , address _colony ) public stoppable onlyMiningChain {
371+ uint256 nextUpdateNumber = reputationUpdateCount[_chainId][_colony] + 1 ;
372+ // Bridged update must exist
373+ require (pendingReputationUpdates[_chainId][_colony][nextUpdateNumber].colony != address (0x00 ), "colony-network-next-update-does-not-exist " );
374+ // It should be the next one
375+ assert (pendingReputationUpdates[_chainId][_colony][nextUpdateNumber - 1 ].colony == address (0x00 ));
376+
377+ PendingReputationUpdate storage pendingUpdate = pendingReputationUpdates[_chainId][_colony][nextUpdateNumber];
378+
379+ // Skill creation must have been bridged
380+ require (networkSkillCounts[pendingUpdate.skillId >> 128 ] >= pendingUpdate.skillId, "colony-network-invalid-skill-id " );
381+
382+ uint128 nParents = skills[pendingUpdate.skillId].nParents;
329383 // We only update child skill reputation if the update is negative, otherwise just set nChildren to 0 to save gas
330- uint128 nChildren = _amount < 0 ? skills[_skillId].nChildren : 0 ;
384+ uint128 nChildren = pendingUpdate.amount < 0 ? skills[pendingUpdate.skillId].nChildren : 0 ;
385+
386+ int256 updateAmount = decayReputation (pendingUpdate.amount, pendingUpdate.timestamp);
387+
388+ reputationUpdateCount[_chainId][_colony] += 1 ;
389+ address user = pendingUpdate.user;
390+ uint256 skillId = pendingUpdate.skillId;
391+ delete pendingReputationUpdates[_chainId][_colony][nextUpdateNumber];
331392
332393 IReputationMiningCycle (inactiveReputationMiningCycle).appendReputationUpdateLog (
333- _user ,
334- _amount ,
335- _skillId ,
394+ user ,
395+ updateAmount ,
396+ skillId ,
336397 _colony,
337398 nParents,
338399 nChildren
339400 );
340401 }
341402
403+ function getPendingReputationUpdate (uint256 _chainId , address _colony , uint256 _updateNumber ) public view onlyMiningChain returns (PendingReputationUpdate memory ) {
404+ return pendingReputationUpdates[_chainId][_colony][_updateNumber];
405+ }
406+
407+ function getBridgedReputationUpdateCount (uint256 _chainId , address _colony ) public view returns (uint256 ) {
408+ return reputationUpdateCount[_chainId][_colony];
409+ }
410+
342411 function appendReputationUpdateLog (address _user , int _amount , uint _skillId ) public
343412 stoppable
344413 calledByColony
@@ -370,15 +439,21 @@ contract ColonyNetwork is BasicMetaTransaction, ColonyNetworkStorage, Multicall
370439 // Call appendReputationUpdateLogFromBridge on metacolony on xdai
371440 // TODO: Maybe force to be set on deployment?
372441 require (miningBridgeAddress != address (0x0 ), "colony-network-foreign-bridge-not-set " );
442+
443+ reputationUpdateCount[getChainId ()][msgSender ()] += 1 ;
373444 // require(bridgeData[bridgeAddress].chainId == MINING_CHAIN_ID, "colony-network-foreign-bridge-not-set-correctly");
374445 // Build the transaction we're going to send to the bridge
375446 bytes memory payload = abi.encodePacked (
376447 bridgeData[miningBridgeAddress].updateLogBefore,
377- abi.encodeWithSignature ("appendReputationUpdateLogFromBridge(address,address,int256,uint256) " , msgSender (), _user, _amount, _skillId),
448+ abi.encodeWithSignature ("appendReputationUpdateLogFromBridge(address,address,int256,uint256,uint256 ) " , msgSender (), _user, _amount, _skillId, reputationUpdateCount[ getChainId ()][ msgSender ()] ),
378449 bridgeData[miningBridgeAddress].updateLogAfter
379450 );
380451 (bool success , ) = miningBridgeAddress.call (payload);
381- // TODO: Do we care about success here? (probably not)
452+ if (! success || ! isContract (miningBridgeAddress)) {
453+ // Store to resend later
454+ pendingReputationUpdates[getChainId ()][msgSender ()][reputationUpdateCount[getChainId ()][msgSender ()]] = PendingReputationUpdate (msgSender (), _user, _amount, _skillId, block .timestamp );
455+ }
456+ // TODO: How do we emit events here?
382457 }
383458 }
384459
@@ -438,4 +513,39 @@ contract ColonyNetwork is BasicMetaTransaction, ColonyNetworkStorage, Multicall
438513 }
439514 }
440515 }
516+
517+ function isContract (address addr ) internal returns (bool ) {
518+ uint256 size;
519+ assembly { size := extcodesize (addr) }
520+ return size > 0 ;
521+ }
522+
523+
524+ // Mining cycle decay constants
525+ // Note that these values and the mining window size (defined in ReputationMiningCycleCommon)
526+ // need to be consistent with each other, but are not checked, in order for the decay
527+ // rate to be as-expected.
528+
529+ int256 constant DECAY_NUMERATOR = 999679150010889 ; // 1-hr mining cycle
530+ int256 constant DECAY_DENOMINATOR = 1000000000000000 ;
531+ uint256 constant DECAY_PERIOD = 1 hours ;
532+ function decayReputation (int256 _reputation , uint256 _since ) internal view returns (int256 decayedReputation ) {
533+ uint256 decayEpochs = (block .timestamp - _since) / DECAY_PERIOD;
534+ int256 adjustedNumerator = DECAY_NUMERATOR;
535+
536+ // This algorithm successively doubles the decay factor while halving the number of epochs
537+ // This allows us to perform the decay in O(log(n)) time
538+ // For example, a decay of 50 epochs would be applied as (k**2)(k**16)(k**32)
539+ while (decayEpochs > 0 ){
540+ // slither-disable-next-line weak-prng
541+ if (decayEpochs % 2 >= 1 ) {
542+ // slither-disable-next-line divide-before-multiply
543+ _reputation = _reputation * adjustedNumerator / DECAY_DENOMINATOR;
544+ }
545+ // slither-disable-next-line divide-before-multiply
546+ adjustedNumerator = adjustedNumerator * adjustedNumerator / DECAY_DENOMINATOR;
547+ decayEpochs >>= 1 ;
548+ }
549+ return _reputation;
550+ }
441551}
0 commit comments