Skip to content

Commit

Permalink
Root has to allow domains to receive reputation-earning tokens
Browse files Browse the repository at this point in the history
  • Loading branch information
area committed Oct 14, 2024
1 parent 59588fd commit 689000d
Show file tree
Hide file tree
Showing 7 changed files with 265 additions and 14 deletions.
11 changes: 1 addition & 10 deletions contracts/colony/Colony.sol
Original file line number Diff line number Diff line change
Expand Up @@ -320,16 +320,7 @@ contract Colony is BasicMetaTransaction, Multicall, ColonyStorage, PatriciaTreeP
ColonyAuthority colonyAuthority = ColonyAuthority(address(authority));
bytes4 sig;

sig = bytes4(keccak256("cancelExpenditureViaArbitration(uint256,uint256,uint256)"));
colonyAuthority.setRoleCapability(uint8(ColonyRole.Arbitration), address(this), sig, true);

sig = bytes4(keccak256("finalizeExpenditureViaArbitration(uint256,uint256,uint256)"));
colonyAuthority.setRoleCapability(uint8(ColonyRole.Arbitration), address(this), sig, true);

sig = bytes4(keccak256("setColonyBridgeAddress(address)"));
colonyAuthority.setRoleCapability(uint8(ColonyRole.Root), address(this), sig, true);

sig = bytes4(keccak256("initialiseReputationMining(uint256,bytes32,uint256)"));
sig = bytes4(keccak256("editAllowedDomainTokenReceipt(uint256,address,uint256,bool)"));
colonyAuthority.setRoleCapability(uint8(ColonyRole.Root), address(this), sig, true);
}

Expand Down
3 changes: 3 additions & 0 deletions contracts/colony/ColonyAuthority.sol
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,9 @@ contract ColonyAuthority is CommonAuthority {
addRoleCapability(ARBITRATION_ROLE, "finalizeExpenditureViaArbitration(uint256,uint256,uint256)");
addRoleCapability(ROOT_ROLE, "setColonyBridgeAddress(address)");
addRoleCapability(ROOT_ROLE, "initialiseReputationMining(uint256,bytes32,uint256)");

// Added in colony v??
addRoleCapability(ROOT_ROLE, "editAllowedDomainTokenReceipt(uint256,address,uint256,bool)");
}

function addRoleCapability(uint8 role, bytes memory sig) private {
Expand Down
50 changes: 48 additions & 2 deletions contracts/colony/ColonyFunding.sol
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,59 @@ contract ColonyFunding is
nonRewardPotsTotal[_token] += remainder;

fundingPots[0].balance[_token] += feeToPay;
fundingPots[fundingPotId].balance[_token] += remainder;

if (tokenEarnsReputationOnPayout(_token)) {
// If token earns reputation, we only allow up to the approved amount to be received
uint256 approvedAmount = domainReputationTokenApprovals[_domainId][_token];
if (approvedAmount < remainder) {
fundingPots[fundingPotId].balance[_token] += approvedAmount;
Domain storage rootDomain = domains[1];
// And the rest goes to the root pot
fundingPots[rootDomain.fundingPotId].balance[_token] += remainder - approvedAmount;
domainReputationTokenApprovals[_domainId][_token] = 0;
emit DomainFundsClaimed(msgSender(), _token, _domainId, feeToPay, approvedAmount);
emit ColonyFundsClaimed(msgSender(), _token, 0, remainder - approvedAmount);
} else {
fundingPots[fundingPotId].balance[_token] += remainder;
domainReputationTokenApprovals[_domainId][_token] -= remainder;
emit DomainFundsClaimed(msgSender(), _token, _domainId, feeToPay, remainder);
}
} else {
fundingPots[fundingPotId].balance[_token] += remainder;
emit DomainFundsClaimed(msgSender(), _token, _domainId, feeToPay, remainder);
}

// Claim funds

DomainTokenReceiver(domainTokenReceiverAddress).transferToColony(_token);
}

function tokenEarnsReputationOnPayout(address _token) internal view returns (bool) {
return _token == token;
}

emit DomainFundsClaimed(msgSender(), _token, _domainId, feeToPay, remainder);
function editAllowedDomainTokenReceipt(
uint256 _domainId,
address _token,
uint256 _amount,
bool _add
) public stoppable auth {
require(domainExists(_domainId), "colony-funding-domain-does-not-exist");
require(tokenEarnsReputationOnPayout(_token), "colony-funding-token-does-not-earn-reputation");
if (_add) {
domainReputationTokenApprovals[_domainId][_token] += _amount;
} else {
domainReputationTokenApprovals[_domainId][_token] -= _amount;
}
}

function getAllowedDomainTokenReceipt(
uint256 _domainId,
address _token
) public view returns (uint256) {
require(domainExists(_domainId), "colony-funding-domain-does-not-exist");
require(tokenEarnsReputationOnPayout(_token), "colony-funding-token-does-not-earn-reputation");
return domainReputationTokenApprovals[_domainId][_token];
}

function getNonRewardPotsTotal(address _token) public view returns (uint256) {
Expand Down
3 changes: 3 additions & 0 deletions contracts/colony/ColonyStorage.sol
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ contract ColonyStorage is ColonyDataTypes, ColonyNetworkDataTypes, DSMath, Commo
mapping(uint256 => bool) DEPRECATED_localSkills; // Storage slot 37
mapping(uint256 => LocalSkill) localSkills; // Storage slot 38

// Mapping of domain id => token address => approval to receive if reputation-earning
mapping(uint256 => mapping(address => uint256)) domainReputationTokenApprovals; // Storage slot 39

// Constants

uint256 constant MAX_PAYOUT = 2 ** 128 - 1; // 340,282,366,920,938,463,463 WADs
Expand Down
21 changes: 21 additions & 0 deletions contracts/colony/IColony.sol
Original file line number Diff line number Diff line change
Expand Up @@ -852,6 +852,27 @@ interface IColony is ColonyDataTypes, IRecovery, IBasicMetaTransaction, IMultica
/// @param _domainId Id of the domain
function claimDomainFunds(address _token, uint256 _domainId) external;

/// @notice Add or remove an amount from the amount of a reputation earning token that a domain can receive
/// @param _domainId Id of the domain
/// @param _token Address of the token
/// @param _amount Amount to add or remove
/// @param _add Whether to add or remove the amount. True is add, false is remove
function editAllowedDomainTokenReceipt(
uint256 _domainId,
address _token,
uint256 _amount,
bool _add
) external;

/// @notice Get the amount of a reputation earning token that a domain can receive
/// @param _domainId Id of the domain
/// @param _token Address of the token
/// @return uint256 amount Amount of the token that the domain can receive
function getAllowedDomainTokenReceipt(
uint256 _domainId,
address _token
) external view returns (uint256);

/// @notice Get the total amount of tokens `_token` minus amount reserved to be paid to the reputation and token holders as rewards.
/// @param _token Address of the token, `0x0` value indicates Ether
/// @return amount Total amount of tokens in funding pots other than the rewards pot (id 0)
Expand Down
33 changes: 33 additions & 0 deletions docs/interfaces/icolony.md
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,21 @@ Deprecate a local skill for the colony. Secured function to authorised members.
|deprecated|bool|Deprecation status to set for the skill


### `editAllowedDomainTokenReceipt(uint256 _domainId, address _token, uint256 _amount, bool _add)`

Add or remove an amount from the amount of a reputation earning token that a domain can receive


**Parameters**

|Name|Type|Description|
|---|---|---|
|_domainId|uint256|Id of the domain
|_token|address|Address of the token
|_amount|uint256|Amount to add or remove
|_add|bool|Whether to add or remove the amount. True is add, false is remove


### `editColony(string memory _metadata)`

Called to change the metadata associated with a colony. Expected to be a IPFS hash of a JSON blob, but not enforced to any degree by the contracts
Expand Down Expand Up @@ -396,6 +411,24 @@ A function to be called after an upgrade has been done from v2 to v3.



### `getAllowedDomainTokenReceipt(uint256 _domainId, address _token):uint256 uint256`

Get the amount of a reputation earning token that a domain can receive


**Parameters**

|Name|Type|Description|
|---|---|---|
|_domainId|uint256|Id of the domain
|_token|address|Address of the token

**Return Parameters**

|Name|Type|Description|
|---|---|---|
|uint256|uint256|amount Amount of the token that the domain can receive

### `getApproval(address _user, address _obligator, uint256 _domainId):uint256 approval`

View an approval to obligate tokens.
Expand Down
158 changes: 156 additions & 2 deletions test/contracts-network/colony-funding.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ const {
SLOT2,
} = require("../../helpers/constants");

const { fundColonyWithTokens, setupRandomColony, makeExpenditure, setupFundedExpenditure } = require("../../helpers/test-data-generator");
const {
fundColonyWithTokens,
setupRandomColony,
makeExpenditure,
setupFundedExpenditure,
setupClaimedExpenditure,
} = require("../../helpers/test-data-generator");
const { getTokenArgs, checkErrorRevert, web3GetBalance, removeSubdomainLimit, expectEvent } = require("../../helpers/test-helper");
const { setupDomainTokenReceiverResolver } = require("../../helpers/upgradable-contracts");

Expand Down Expand Up @@ -421,7 +427,7 @@ contract("Colony Funding", (accounts) => {

it("should correctly send whitelisted tokens to the Metacolony", async () => {
await fundColonyWithTokens(colony, token, INITIAL_FUNDING);

const currentFee = await colonyNetwork.getFeeInverse();
await metaColony.setNetworkFeeInverse(1); // 100% to fees

const expenditureId = await setupFundedExpenditure({ colonyNetwork, colony });
Expand All @@ -438,6 +444,8 @@ contract("Colony Funding", (accounts) => {
await colony.claimExpenditurePayout(expenditureId, SLOT2, token.address);
const metaColonyBalanceAfter = await token.balanceOf(metaColony.address);
expect(metaColonyBalanceAfter.sub(metaColonyBalanceBefore)).to.eq.BN(WORKER_PAYOUT);

await metaColony.setNetworkFeeInverse(currentFee); // Restore fees
});
});

Expand Down Expand Up @@ -602,6 +610,152 @@ contract("Colony Funding", (accounts) => {
expect(nonRewardPotsTotalAfter.sub(nonRewardPotsTotalBefore)).to.eq.BN(99);
});

it("when receiving native (reputation-earning) token, if no approval present for domain, all are received by root domain", async () => {
// Get address for domain 2
await colony.addDomain(1, UINT256_MAX, 1);
const receiverAddress = await colonyNetwork.getDomainTokenReceiverAddress(colony.address, 2);
await colony.mintTokens(WAD.muln(100));
await colony.claimColonyFunds(token.address);
const domain1 = await colony.getDomain(1);

// Pay the tokens to the domain
await setupClaimedExpenditure({
colonyNetwork,
colony,
domainId: 1,
manager: MANAGER,
managerPayout: 1000,
evaluatorPayout: 0,
workerPayout: 0,
});

// Send 100 to the domain
await token.transfer(receiverAddress, 100);

// Now test what happens when we claim them

const domain = await colony.getDomain(2);
const domainPotBalanceBefore = await colony.getFundingPotBalance(domain.fundingPotId, token.address);
const nonRewardPotsTotalBefore = await colony.getNonRewardPotsTotal(token.address);
const rootDomainPotBalanceBefore = await colony.getFundingPotBalance(domain1.fundingPotId, token.address);

// Claim the funds
await colony.claimDomainFunds(token.address, 2);

const domainPotBalanceAfter = await colony.getFundingPotBalance(domain.fundingPotId, token.address);
const nonRewardPotsTotalAfter = await colony.getNonRewardPotsTotal(token.address);
const rootDomainPotBalanceAfter = await colony.getFundingPotBalance(domain1.fundingPotId, token.address);

// Check the balance of the domain
expect(domainPotBalanceAfter.sub(domainPotBalanceBefore)).to.eq.BN(0);
expect(nonRewardPotsTotalAfter.sub(nonRewardPotsTotalBefore)).to.eq.BN(99);
expect(rootDomainPotBalanceAfter.sub(rootDomainPotBalanceBefore)).to.eq.BN(99);
});

it(`when receiving native (reputation-earning) token, if partial approval present for domain,
tokens are split between intended domain and root`, async () => {
// Get address for domain 2
await colony.addDomain(1, UINT256_MAX, 1);
const receiverAddress = await colonyNetwork.getDomainTokenReceiverAddress(colony.address, 2);
await colony.mintTokens(WAD.muln(100));
await colony.claimColonyFunds(token.address);
const domain1 = await colony.getDomain(1);

// Pay the tokens to the domain
await setupClaimedExpenditure({
colonyNetwork,
colony,
domainId: 1,
manager: MANAGER,
tokenAddress: token.address,
managerPayout: 1000,
evaluatorPayout: 0,
workerPayout: 0,
});

// Send 100 to the domain
await token.transfer(receiverAddress, 100);

// Approve 70 for the domain
await colony.editAllowedDomainTokenReceipt(2, token.address, 70, true);
let allowedReceipt = await colony.getAllowedDomainTokenReceipt(2, token.address);
expect(allowedReceipt).to.eq.BN(70);

// Now test what happens when we claim them

const domain = await colony.getDomain(2);
const domainPotBalanceBefore = await colony.getFundingPotBalance(domain.fundingPotId, token.address);
const nonRewardPotsTotalBefore = await colony.getNonRewardPotsTotal(token.address);
const rootDomainPotBalanceBefore = await colony.getFundingPotBalance(domain1.fundingPotId, token.address);

// Claim the funds
await colony.claimDomainFunds(token.address, 2);

const domainPotBalanceAfter = await colony.getFundingPotBalance(domain.fundingPotId, token.address);
const nonRewardPotsTotalAfter = await colony.getNonRewardPotsTotal(token.address);
const rootDomainPotBalanceAfter = await colony.getFundingPotBalance(domain1.fundingPotId, token.address);

// Check the balance of the domain
expect(domainPotBalanceAfter.sub(domainPotBalanceBefore)).to.eq.BN(70);
expect(nonRewardPotsTotalAfter.sub(nonRewardPotsTotalBefore)).to.eq.BN(99);
expect(rootDomainPotBalanceAfter.sub(rootDomainPotBalanceBefore)).to.eq.BN(29);

allowedReceipt = await colony.getAllowedDomainTokenReceipt(2, token.address);
expect(allowedReceipt).to.eq.BN(0);
});

it(`when receiving native (reputation-earning) token, if full approval present for domain,
tokens are received by domain`, async () => {
// Get address for domain 2
await colony.addDomain(1, UINT256_MAX, 1);
const receiverAddress = await colonyNetwork.getDomainTokenReceiverAddress(colony.address, 2);
await colony.mintTokens(WAD.muln(100));
await colony.claimColonyFunds(token.address);
const domain1 = await colony.getDomain(1);

// Pay the tokens to the domain
await setupClaimedExpenditure({
colonyNetwork,
colony,
domainId: 1,
manager: MANAGER,
tokenAddress: token.address,
managerPayout: 1000,
evaluatorPayout: 0,
workerPayout: 0,
});

// Send 100 to the domain
await token.transfer(receiverAddress, 100);

// Approve 250 for the domain
await colony.editAllowedDomainTokenReceipt(2, token.address, 250, true);
let allowedReceipt = await colony.getAllowedDomainTokenReceipt(2, token.address);
expect(allowedReceipt).to.eq.BN(250);

// Now test what happens when we claim them

const domain = await colony.getDomain(2);
const domainPotBalanceBefore = await colony.getFundingPotBalance(domain.fundingPotId, token.address);
const nonRewardPotsTotalBefore = await colony.getNonRewardPotsTotal(token.address);
const rootDomainPotBalanceBefore = await colony.getFundingPotBalance(domain1.fundingPotId, token.address);

// Claim the funds
await colony.claimDomainFunds(token.address, 2);

const domainPotBalanceAfter = await colony.getFundingPotBalance(domain.fundingPotId, token.address);
const nonRewardPotsTotalAfter = await colony.getNonRewardPotsTotal(token.address);
const rootDomainPotBalanceAfter = await colony.getFundingPotBalance(domain1.fundingPotId, token.address);

// Check the balance of the domain
expect(domainPotBalanceAfter.sub(domainPotBalanceBefore)).to.eq.BN(99);
expect(nonRewardPotsTotalAfter.sub(nonRewardPotsTotalBefore)).to.eq.BN(99);
expect(rootDomainPotBalanceAfter.sub(rootDomainPotBalanceBefore)).to.eq.BN(0);

allowedReceipt = await colony.getAllowedDomainTokenReceipt(2, token.address);
expect(allowedReceipt).to.eq.BN(151);
});

it("should not be able to claim funds for a domain that does not exist", async () => {
await checkErrorRevert(colony.claimDomainFunds(ethers.constants.AddressZero, 2), "colony-funding-domain-does-not-exist");
});
Expand Down

0 comments on commit 689000d

Please sign in to comment.