Skip to content

Commit abf0f20

Browse files
committed
Factor out reputation scaling function
1 parent 88cba67 commit abf0f20

7 files changed

Lines changed: 154 additions & 61 deletions

File tree

contracts/colony/Colony.sol

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ contract Colony is BasicMetaTransaction, Multicall, ColonyStorage, PatriciaTreeP
114114
// After doing all the local storage changes, then do all the external calls
115115
for (uint256 i = 0; i < _users.length; i++) {
116116
require(ERC20Extended(token).transfer(_users[i], uint256(_amounts[i])), "colony-bootstrap-token-transfer-failed");
117-
int256 tokenScaledReputationAmount = getTokenScaledReputation(_amounts[i], token);
117+
uint256 scaleFactor = tokenReputationRates[token]; // NB This is a WAD
118+
int256 tokenScaledReputationAmount = scaleReputation(_amounts[i], scaleFactor);
118119
IColonyNetwork(colonyNetworkAddress).appendReputationUpdateLog(_users[i], tokenScaledReputationAmount, domains[1].skillId);
119120
}
120121

contracts/colony/ColonyFunding.sol

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -180,11 +180,10 @@ contract ColonyFunding is ColonyStorage { // ignore-swc-123
180180
reputation = negative ? reputation + payout : reputation - payout;
181181
}
182182

183+
uint256 scaleFactor = tokenReputationRates[tokenAddress]; // NB This is a WAD
183184
// We may lose one atom of reputation here :sad:
184-
return getTokenScaledReputation(
185-
int256(reputation / 2) * (negative ? int256(-1) : int256(1)),
186-
tokenAddress
187-
);
185+
186+
return scaleReputation(int256(reputation / 2) * (negative ? int256(-1) : int256(1)), scaleFactor);
188187
}
189188

190189
/// @notice For owners to update payouts with one token and many slots
@@ -272,7 +271,7 @@ contract ColonyFunding is ColonyStorage { // ignore-swc-123
272271
// Process reputation updates if relevant for token being paid out
273272
if (tokenReputationRates[_token] > 0 && !isExtension(slot.recipient)) {
274273
IColonyNetwork colonyNetworkContract = IColonyNetwork(colonyNetworkAddress);
275-
int256 tokenScaledReputationAmount = getTokenScaledReputation(int256(repPayout), _token);
274+
int256 tokenScaledReputationAmount = scaleReputation(int256(repPayout), tokenReputationRates[_token]);
276275

277276
colonyNetworkContract.appendReputationUpdateLog(slot.recipient, tokenScaledReputationAmount, domains[expenditure.domainId].skillId);
278277
if (slot.skills.length > 0 && slot.skills[0] > 0) {
@@ -313,7 +312,8 @@ contract ColonyFunding is ColonyStorage { // ignore-swc-123
313312

314313
if (!isExtension(payment.recipient)) {
315314

316-
int256 tokenScaledReputationAmount = getTokenScaledReputation(int256(fundingPot.payouts[_token]), _token);
315+
uint256 scaleFactor = tokenReputationRates[_token]; // NB This is a WAD
316+
int256 tokenScaledReputationAmount = scaleReputation(int256(fundingPot.payouts[_token]), scaleFactor);
317317

318318
// Todo: Is this equality right?
319319
if (tokenScaledReputationAmount > 0){

contracts/colony/ColonyStorage.sol

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pragma solidity 0.8.20;
1919
pragma experimental ABIEncoderV2;
2020

2121
import "./../../lib/dappsys/math.sol";
22+
import "./../common/ScaleReputation.sol";
2223
import "./../common/CommonStorage.sol";
2324
import "./../common/ERC20Extended.sol";
2425
import "./../colonyNetwork/IColonyNetwork.sol";
@@ -31,7 +32,7 @@ import "./ColonyDataTypes.sol";
3132
// ignore-file-swc-108
3233

3334

34-
contract ColonyStorage is ColonyDataTypes, ColonyNetworkDataTypes, DSMath, CommonStorage {
35+
contract ColonyStorage is ColonyDataTypes, ColonyNetworkDataTypes, DSMath, CommonStorage, ScaleReputation {
3536
uint256 constant COLONY_NETWORK_SLOT = 6;
3637
uint256 constant ROOT_LOCAL_SKILL_SLOT = 36;
3738

@@ -360,32 +361,4 @@ contract ColonyStorage is ColonyDataTypes, ColonyNetworkDataTypes, DSMath, Commo
360361
success := call(gas(), to, value, add(data, 0x20), mload(data), 0, 0)
361362
}
362363
}
363-
364-
365-
uint256 constant INT128_MAX_AS_UINT256 = uint256(uint128(type(int128).max));
366-
367-
function getTokenScaledReputation(int256 _amount, address _token) internal view returns (int256) {
368-
uint256 scaleFactor = tokenReputationRates[_token]; // NB This is a WAD
369-
if (scaleFactor == 0) { return 0; }
370-
371-
// Check if too large for scaling
372-
int256 amount;
373-
int256 absAmount;
374-
if (_amount == type(int256).min){
375-
absAmount = type(int256).max; // Off by one, but best we can do - probably gets capped anyway
376-
} else {
377-
absAmount = _amount >= 0 ? _amount : -_amount;
378-
}
379-
380-
int256 sgnAmount = _amount >= 0 ? int(1) : -1;
381-
382-
383-
if (wdiv(INT128_MAX_AS_UINT256, scaleFactor) < uint256(absAmount)){
384-
return sgnAmount == 1 ? type(int128).max : type(int128).min;
385-
} else {
386-
amount = int256(wmul(scaleFactor, uint256(absAmount))) * sgnAmount;
387-
}
388-
389-
return amount;
390-
}
391364
}

contracts/colonyNetwork/ColonyNetwork.sol

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ pragma solidity 0.8.20;
1919
pragma experimental "ABIEncoderV2";
2020

2121
import "./../common/BasicMetaTransaction.sol";
22+
import "./../common/ScaleReputation.sol";
2223
import "./../reputationMiningCycle/IReputationMiningCycle.sol";
2324
import "./ColonyNetworkStorage.sol";
2425
import "./../common/Multicall.sol";
2526
import "./../colony/ColonyDataTypes.sol";
2627

2728

28-
contract ColonyNetwork is ColonyDataTypes, BasicMetaTransaction, ColonyNetworkStorage, Multicall {
29+
contract ColonyNetwork is ColonyDataTypes, BasicMetaTransaction, ColonyNetworkStorage, Multicall, ScaleReputation {
2930

3031
function isColony(address _colony) public view returns (bool) {
3132
return _isColony[_colony];
@@ -226,31 +227,8 @@ contract ColonyNetwork is ColonyDataTypes, BasicMetaTransaction, ColonyNetworkSt
226227
}
227228

228229
uint256 scaleFactor = getSkillReputationScaling(_skillId);
229-
if (scaleFactor == 0){
230-
// Similarly, if the amount is 0 because of scaling, we short circuit it
231-
return;
232-
}
233-
234-
// Check if too large for scaling
235-
int256 amount;
236-
int256 absAmount;
237-
if (_amount == type(int256).min){
238-
absAmount = type(int256).max; // Off by one, but best we can do - probably gets capped anyway
239-
} else {
240-
absAmount = _amount >= 0 ? _amount : -_amount;
241-
}
242230

243-
int256 sgnAmount = _amount >= 0 ? int(1) : -1;
244-
245-
if (type(uint256).max / scaleFactor < uint256(absAmount)){
246-
if (sgnAmount == 1){
247-
amount = type(int128).max;
248-
} else {
249-
amount = type(int128).min;
250-
}
251-
} else {
252-
amount = int256(wmul(scaleFactor, uint256(absAmount))) * sgnAmount;
253-
}
231+
int256 amount = scaleReputation(_amount, scaleFactor);
254232

255233
uint128 nParents = skills[_skillId].nParents;
256234
// We only update child skill reputation if the update is negative, otherwise just set nChildren to 0 to save gas
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
This file is part of The Colony Network.
3+
4+
The Colony Network is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
The Colony Network is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with The Colony Network. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
pragma solidity 0.8.20; // ignore-swc-103
19+
import "../../lib/dappsys/math.sol";
20+
21+
contract ScaleReputation is DSMath {
22+
// Note that scaleFactor should be a WAD.
23+
function scaleReputation(int256 reputationAmount, uint256 scaleFactor) internal pure returns (int256) {
24+
if (reputationAmount == 0 || scaleFactor == 0) {
25+
return 0;
26+
}
27+
28+
int256 scaledReputation;
29+
int256 absAmount;
30+
31+
if (reputationAmount == type(int256).min){
32+
absAmount = type(int256).max; // Off by one, but best we can do - probably gets capped anyway
33+
} else {
34+
absAmount = reputationAmount >= 0 ? reputationAmount : -reputationAmount;
35+
}
36+
37+
int256 sgnAmount = reputationAmount >= 0 ? int(1) : -1;
38+
39+
// Guard against overflows during calculation with wmul
40+
if (type(uint256).max / scaleFactor < uint256(absAmount)){
41+
if (sgnAmount == 1){
42+
scaledReputation = type(int128).max;
43+
} else {
44+
scaledReputation = type(int128).min;
45+
}
46+
} else {
47+
scaledReputation = int256(wmul(scaleFactor, uint256(absAmount))) * sgnAmount;
48+
// Cap inside the range of int128, as we do for all reputations
49+
scaledReputation = imax(type(int128).min, scaledReputation);
50+
scaledReputation = imin(type(int128).max, scaledReputation);
51+
}
52+
return scaledReputation;
53+
}
54+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
This file is part of The Colony Network.
3+
4+
The Colony Network is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
The Colony Network is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with The Colony Network. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
18+
pragma solidity 0.8.20; // ignore-swc-103
19+
import "../common/ScaleReputation.sol";
20+
21+
contract ScaleReputationTest is ScaleReputation {
22+
23+
function scaleReputationPublic(int256 reputationAmount, uint256 scaleFactor) public pure returns (int256) {
24+
return scaleReputation(reputationAmount, scaleFactor);
25+
}
26+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/* globals artifacts */
2+
3+
const chai = require("chai");
4+
const bnChai = require("bn-chai");
5+
6+
const { INT256_MAX, INT256_MIN, INT128_MIN, INT128_MAX, WAD } = require("../../helpers/constants");
7+
8+
const { expect } = chai;
9+
chai.use(bnChai(web3.utils.BN));
10+
11+
const ScaleReputationTest = artifacts.require("ScaleReputationTest");
12+
13+
let scaleReputationTest;
14+
15+
contract("ScaleReputation", () => {
16+
before(async () => {
17+
scaleReputationTest = await ScaleReputationTest.new();
18+
});
19+
20+
describe("when scaling reputation", () => {
21+
it("should scale reputation up", async () => {
22+
const scaled = await scaleReputationTest.scaleReputationPublic(100, WAD.muln(2));
23+
expect(scaled).to.eq.BN(200);
24+
});
25+
26+
it("should scale reputation down", async () => {
27+
const scaled = await scaleReputationTest.scaleReputationPublic(100, WAD.divn(2));
28+
expect(scaled).to.eq.BN(50);
29+
});
30+
31+
it("should cap negatively", async () => {
32+
const scaled = await scaleReputationTest.scaleReputationPublic(INT128_MAX.subn(10), WAD.muln(2));
33+
expect(scaled).to.eq.BN(INT128_MAX);
34+
});
35+
36+
it("should cap positively", async () => {
37+
const scaled = await scaleReputationTest.scaleReputationPublic(INT128_MIN.addn(10), WAD.muln(2));
38+
expect(scaled).to.eq.BN(INT128_MIN);
39+
});
40+
41+
it("deal with calculations that would arithmetically overflow", async () => {
42+
const scaled = await scaleReputationTest.scaleReputationPublic(INT256_MAX.subn(10), WAD.subn(1));
43+
expect(scaled).to.eq.BN(INT128_MAX);
44+
});
45+
46+
it("deal with calculations that would arithmetically underflow", async () => {
47+
const scaled = await scaleReputationTest.scaleReputationPublic(INT256_MIN.addn(10), WAD.subn(1));
48+
expect(scaled).to.eq.BN(INT128_MIN);
49+
});
50+
51+
it("deal with calculations that exceed our reputation cap during calculation, but not once calculation is complete", async () => {
52+
const scaled = await scaleReputationTest.scaleReputationPublic(INT128_MIN.addn(10), WAD.subn(1));
53+
expect(scaled).to.be.gt.BN(INT128_MIN);
54+
});
55+
56+
it("deal with calculations that exceed our reputation cap during calculation, but not once calculation is complete", async () => {
57+
const scaled = await scaleReputationTest.scaleReputationPublic(INT128_MAX.subn(10), WAD.subn(1));
58+
expect(scaled).to.be.lt.BN(INT128_MAX);
59+
});
60+
});
61+
});

0 commit comments

Comments
 (0)