Skip to content

Commit 9fb4863

Browse files
committed
Test reputation decay on troubled bridging
1 parent 7bc74b3 commit 9fb4863

File tree

14 files changed

+695
-126
lines changed

14 files changed

+695
-126
lines changed

.solcover.crosschain.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,7 @@ config.providerOptions.network_id = chainId;
2020
config.providerOptions._chainId = chainId;
2121
config.providerOptions._chainIdRpc = chainId;
2222

23-
config.istanbulFolder = `./coverage-cross-chain-${process.env.TRUFFLE_HOME ? "home" : "foreign"}`
24-
23+
config.istanbulFolder = `./coverage-cross-chain-${JSON.parse(process.env.TRUFFLE_FOREIGN) ? "foreign" : "home"}`
2524

2625
function provisionSafeContracts(){
2726
let output;

contracts/colony/Colony.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,7 @@ contract Colony is BasicMetaTransaction, Multicall, ColonyStorage, PatriciaTreeP
173173
function addGlobalSkill() public
174174
stoppable
175175
auth
176+
onlyMiningChain
176177
returns (uint256)
177178
{
178179
return IColonyNetwork(colonyNetworkAddress).addSkill(0); // ignore-swc-107

contracts/colonyNetwork/ColonyNetwork.sol

Lines changed: 132 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -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
}

contracts/colonyNetwork/ColonyNetworkDataTypes.sol

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,12 @@ interface ColonyNetworkDataTypes {
189189
bytes setReputationRootHashBefore;
190190
bytes setReputationRootHashAfter;
191191
}
192+
193+
struct PendingReputationUpdate {
194+
address colony;
195+
address user;
196+
int256 amount;
197+
uint skillId;
198+
uint256 timestamp;
199+
}
192200
}

contracts/colonyNetwork/ColonyNetworkDeployer.sol

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,7 @@ contract ColonyNetworkDeployer is ColonyNetworkStorage {
153153

154154
// Initialise the domain tree with defaults by just incrementing the skillCount
155155
skillCount += 1;
156-
// Bridge if necessary
157-
if (!isMiningChain()) {
158-
IColonyNetwork(address(this)).bridgeSkill(skillCount);
159-
}
156+
IColonyNetwork(address(this)).bridgeSkillIfNotMiningChain(skillCount);
160157
colonyCount += 1;
161158
colonies[colonyCount] = address(colony);
162159
_isColony[address(colony)] = true;

contracts/colonyNetwork/ColonyNetworkMining.sol

Lines changed: 2 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ contract ColonyNetworkMining is ColonyNetworkStorage {
8585

8686
// Well this is a weird hack to need
8787
function newAddressArray() pure internal returns (address[] memory) {}
88-
function bridgeSetReputationRootHash(bytes32 newHash, uint256 newNLeaves) onlyNotMiningChain stoppable public {
88+
function setReputationRootHashFromBridge(bytes32 newHash, uint256 newNLeaves) onlyNotMiningChain stoppable public {
8989
require(bridgeData[msgSender()].chainId != 0, "colony-network-not-known-bridge");
9090
reputationRootHash = newHash;
9191
reputationRootHashNLeaves = newNLeaves;
@@ -97,7 +97,7 @@ contract ColonyNetworkMining is ColonyNetworkStorage {
9797
require(bridgeData[bridgeAddress].chainId != 0, "colony-network-not-known-bridge");
9898
bytes memory payload = abi.encodePacked(
9999
bridgeData[bridgeAddress].setReputationRootHashBefore,
100-
abi.encodeWithSignature("bridgeSetReputationRootHash(bytes32,uint256)", reputationRootHash, reputationRootHashNLeaves),
100+
abi.encodeWithSignature("setReputationRootHashFromBridge(bytes32,uint256)", reputationRootHash, reputationRootHashNLeaves),
101101
bridgeData[bridgeAddress].setReputationRootHashAfter
102102
);
103103
(bool success, ) = bridgeAddress.call(payload);
@@ -277,14 +277,6 @@ contract ColonyNetworkMining is ColonyNetworkStorage {
277277
return miningStakes[_user];
278278
}
279279

280-
function addBridgeForNetwork(address _bridgeAddress, uint256 _chainId) public always auth {
281-
authorizedBridges[_bridgeAddress] = _chainId;
282-
}
283-
284-
function getAuthorizedBridge(address _bridgeAddress) public view returns (uint256 networkId) {
285-
return authorizedBridges[_bridgeAddress];
286-
}
287-
288280
function burnUnneededRewards(uint256 _amount) public onlyMiningChain stoppable onlyReputationMiningCycle() {
289281
// If there are no rewards to burn, no need to do anything
290282
if (_amount == 0){ return; }

contracts/colonyNetwork/ColonyNetworkStorage.sol

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,16 +107,19 @@ contract ColonyNetworkStorage is ColonyNetworkDataTypes, DSMath, CommonStorage,
107107
// Mining delegation mapping
108108
mapping(address => address) miningDelegators; // Storage slot 42
109109

110-
mapping(address => uint256) authorizedBridges; // Storage slot 43
111-
112-
address miningBridgeAddress; // Storage slot 44
113-
mapping(address => Bridge) bridgeData; // Storage slot 45
110+
address miningBridgeAddress; // Storage slot 43
111+
mapping(address => Bridge) bridgeData; // Storage slot 44
114112

115113
// A mapping that maps network id -> skill count
116-
mapping(uint256 => uint256) networkSkillCounts; // Storage slot 46
114+
mapping(uint256 => uint256) networkSkillCounts; // Storage slot 45
117115
// A mapping that stores pending bridged skill additions that have been bridged out-of-order
118116
// networkId -> skillCount -> parentSkillId
119-
mapping(uint256 => mapping(uint256 => uint256)) pendingSkillAdditions; // Storage slot 47
117+
mapping(uint256 => mapping(uint256 => uint256)) pendingSkillAdditions; // Storage slot 46
118+
119+
// networkId -> colonyAddress -> updateCount -> update
120+
mapping(uint256 => mapping( address => mapping(uint256 => PendingReputationUpdate))) pendingReputationUpdates; // Storage slot 47
121+
// networkID -> colonyAddress -> updateCount
122+
mapping(uint256 => mapping( address => uint256)) reputationUpdateCount; // Storage slot 48
120123

121124
modifier calledByColony() {
122125
require(_isColony[msgSender()], "colony-caller-must-be-colony");
@@ -139,6 +142,7 @@ contract ColonyNetworkStorage is ColonyNetworkDataTypes, DSMath, CommonStorage,
139142
// All colonies are able to manage their Local (domain associated) skills
140143
modifier allowedToAddSkill(bool globalSkill) {
141144
if (globalSkill) {
145+
assert(isMiningChain());
142146
require(msgSender() == metaColony, "colony-must-be-meta-colony");
143147
} else {
144148
require(_isColony[msgSender()] || msgSender() == address(this), "colony-caller-must-be-colony");

0 commit comments

Comments
 (0)