Skip to content

Commit a7f50f2

Browse files
dOrgJelliorenyodfat
authored andcommitted
DAOTracker: A Solution For Indexing New DAOs (#640)
* init * minor changes * compiling * tests passing * DAOTracker tests * linter fix * updates based on feedback * minimize storage * merge * lint fix * pragma solidity ^0.5.11;
1 parent fccac57 commit a7f50f2

24 files changed

+375
-49
lines changed

contracts/universalSchemes/DaoCreator.sol

Lines changed: 17 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ pragma solidity ^0.5.11;
33
import "./UniversalScheme.sol";
44
import "../controller/UController.sol";
55
import "../controller/Controller.sol";
6+
import "../utils/DAOTracker.sol";
67

78

89
/**
910
* @title ControllerCreator for creating a single controller.
1011
*/
11-
1212
contract ControllerCreator {
1313

1414
function create(Avatar _avatar) public returns(address) {
@@ -22,8 +22,6 @@ contract ControllerCreator {
2222
/**
2323
* @title Genesis Scheme that creates organizations
2424
*/
25-
26-
2725
contract DaoCreator {
2826

2927
mapping(address=>address) public locks;
@@ -32,9 +30,13 @@ contract DaoCreator {
3230
event InitialSchemesSet (address _avatar);
3331

3432
ControllerCreator private controllerCreator;
33+
DAOTracker private daoTracker;
3534

36-
constructor(ControllerCreator _controllerCreator) public {
35+
constructor(ControllerCreator _controllerCreator, DAOTracker _daoTracker) public {
36+
require(_controllerCreator != ControllerCreator(0));
37+
require(_daoTracker != DAOTracker(0));
3738
controllerCreator = _controllerCreator;
39+
daoTracker = _daoTracker;
3840
}
3941

4042
/**
@@ -198,16 +200,19 @@ contract DaoCreator {
198200
// Create Controller:
199201
if (UController(0) == _uController) {
200202
controller = ControllerInterface(controllerCreator.create(avatar));
201-
avatar.transferOwnership(address(controller));
202-
// Transfer ownership:
203-
nativeToken.transferOwnership(address(controller));
204-
nativeReputation.transferOwnership(address(controller));
205203
} else {
206204
controller = _uController;
207-
avatar.transferOwnership(address(controller));
208-
// Transfer ownership:
209-
nativeToken.transferOwnership(address(controller));
210-
nativeReputation.transferOwnership(address(controller));
205+
}
206+
207+
// Add the DAO to the tracking registry
208+
daoTracker.track(avatar, controller);
209+
210+
// Transfer ownership:
211+
avatar.transferOwnership(address(controller));
212+
nativeToken.transferOwnership(address(controller));
213+
nativeReputation.transferOwnership(address(controller));
214+
215+
if (controller == _uController) {
211216
_uController.newOrganization(avatar);
212217
}
213218

contracts/utils/DAOTracker.sol

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
pragma solidity ^0.5.11;
2+
3+
import "@daostack/infra/contracts/Reputation.sol";
4+
import "../controller/DAOToken.sol";
5+
import "../controller/Avatar.sol";
6+
import "../controller/ControllerInterface.sol";
7+
import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
8+
9+
/**
10+
* @title An on-chain "source of truth" for what DAOs
11+
* should be index into DAOstack's subgraph.
12+
*/
13+
contract DAOTracker is Ownable {
14+
15+
// `blacklist` the DAO from the subgraph's cache.
16+
// Only able to be set by the owner of the DAOTracker.
17+
mapping(address=>bool) public blacklisted;
18+
19+
event TrackDAO(address indexed _avatar, address _controller, address _reputation, address _daoToken);
20+
event BlacklistDAO(address indexed _avatar, string _explanationHash);
21+
event ResetDAO(address indexed _avatar, string _explanationHash);
22+
23+
modifier onlyAvatarOwner(Avatar avatar) {
24+
require(avatar.owner() == msg.sender,
25+
"The caller must be the owner of the Avatar.");
26+
_;
27+
}
28+
29+
modifier notBlacklisted(Avatar avatar) {
30+
require(blacklisted[address(avatar)] == false,
31+
"The avatar has been blacklisted.");
32+
_;
33+
}
34+
35+
/**
36+
* @dev track a new organization. This function will tell the subgraph
37+
* to start ingesting events from the DAO's contracts.
38+
* NOTE: This function should be called as early as possible in the DAO deployment
39+
* process. **Smart Contract Events that are emitted from blocks prior to this function's
40+
* event being emitted WILL NOT be ingested into the subgraph**, leading to an incorrect
41+
* cache. If this happens to you, please contact the subgraph maintainer. Your DAO will
42+
* need to be added to the subgraph's startup config, and the cache will need to be rebuilt.
43+
* @param _avatar the organization avatar
44+
* @param _controller the organization controller
45+
*/
46+
function track(Avatar _avatar, ControllerInterface _controller)
47+
public
48+
onlyAvatarOwner(_avatar)
49+
notBlacklisted(_avatar) {
50+
// Only allow the information to be set once. In the case of a controller upgrades,
51+
// the subgraph will be updated via the UpgradeController event.
52+
require(_avatar != Avatar(0));
53+
require(_controller != ControllerInterface(0));
54+
55+
emit TrackDAO(
56+
address(_avatar),
57+
address(_controller),
58+
address(_avatar.nativeReputation()),
59+
address(_avatar.nativeToken())
60+
);
61+
}
62+
63+
/**
64+
* @dev blacklist a DAO from the cache. This should be callable by maintainer of the cache.
65+
* Blacklisting can be used to defend against DoS attacks, or to remove spam. In order
66+
* for this blacklisting to take affect within the cache, it would need to be rebuilt.
67+
* @param _avatar the organization avatar
68+
* @param _explanationHash A hash of a document explaining why this DAO was blacklisted
69+
*/
70+
function blacklist(Avatar _avatar, string memory _explanationHash)
71+
public
72+
onlyOwner {
73+
require(_avatar != Avatar(0));
74+
blacklisted[address(_avatar)] = true;
75+
emit BlacklistDAO(address(_avatar), _explanationHash);
76+
}
77+
78+
/**
79+
* @dev reset a DAO in the cache. This should be callable by the maintainer of the cache.
80+
* @param _avatar the organization avatar
81+
* @param _explanationHash A hash of a document explaining why this DAO was reset
82+
*/
83+
function reset(Avatar _avatar, string memory _explanationHash)
84+
public
85+
onlyOwner {
86+
require(_avatar != Avatar(0));
87+
blacklisted[address(_avatar)] = false;
88+
emit ResetDAO(address(_avatar), _explanationHash);
89+
}
90+
}

migrations/2_deploy_organization.js

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ var AbsoluteVote = artifacts.require('./AbsoluteVote.sol');
99
var ContributionReward = artifacts.require('./ContributionReward.sol');
1010
var UpgradeScheme = artifacts.require('./UpgradeScheme.sol');
1111
var ControllerCreator = artifacts.require('./ControllerCreator.sol');
12+
var DAOTracker = artifacts.require('./DAOTracker.sol');
1213
const NULL_ADDRESS = '0x0000000000000000000000000000000000000000';
1314

1415

@@ -35,8 +36,10 @@ var accounts;
3536
//schemeRegistrar, upgradeScheme,globalConstraintRegistrar,simpleICO,contributionReward.
3637
module.exports = async function(deployer) {
3738
deployer.deploy(ControllerCreator, {gas: constants.ARC_GAS_LIMIT}).then(async function(){
39+
await deployer.deploy(DAOTracker, {gas: constants.ARC_GAS_LIMIT});
40+
var daoTracker = await DAOTracker.deployed();
3841
var controllerCreator = await ControllerCreator.deployed();
39-
await deployer.deploy(DaoCreator,controllerCreator.address, {gas: constants.ARC_GAS_LIMIT});
42+
await deployer.deploy(DaoCreator,controllerCreator.address,daoTracker.address, {gas: constants.ARC_GAS_LIMIT});
4043
var daoCreatorInst = await DaoCreator.deployed(controllerCreator.address,{gas: constants.ARC_GAS_LIMIT});
4144
// Create DAOstack:
4245

package-lock.json

Lines changed: 21 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/auction4reputation.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const helpers = require('./helpers');
22
const DaoCreator = artifacts.require("./DaoCreator.sol");
33
const ControllerCreator = artifacts.require("./ControllerCreator.sol");
4+
const DAOTracker = artifacts.require("./DAOTracker.sol");
45
const constants = require('./constants');
56
const ERC20Mock = artifacts.require('./test/ERC20Mock.sol');
67
var Auction4Reputation = artifacts.require("./Auction4Reputation.sol");
@@ -17,7 +18,8 @@ const setup = async function (accounts,
1718
var testSetup = new helpers.TestSetup();
1819
testSetup.biddingToken = await ERC20Mock.new(accounts[0], web3.utils.toWei('100', "ether"));
1920
var controllerCreator = await ControllerCreator.new({gas: constants.ARC_GAS_LIMIT});
20-
testSetup.daoCreator = await DaoCreator.new(controllerCreator.address,{gas:constants.ARC_GAS_LIMIT});
21+
var daoTracker = await DAOTracker.new({gas: constants.ARC_GAS_LIMIT});
22+
testSetup.daoCreator = await DaoCreator.new(controllerCreator.address,daoTracker.address,{gas:constants.ARC_GAS_LIMIT});
2123

2224
testSetup.org = await helpers.setupOrganization(testSetup.daoCreator,accounts[0],1000,1000);
2325
testSetup.auctionsEndTime = (await web3.eth.getBlock("latest")).timestamp + _auctionsEndTime;

test/continuouslockingtoken4reputation.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const helpers = require('./helpers');
22
const DaoCreator = artifacts.require("./DaoCreator.sol");
3+
const DAOTracker = artifacts.require("./DAOTracker.sol");
34
const ControllerCreator = artifacts.require("./ControllerCreator.sol");
45
const constants = require('./constants');
56
const ERC20Mock = artifacts.require('./test/ERC20Mock.sol');
@@ -21,7 +22,8 @@ const setup = async function (accounts,
2122
var testSetup = new helpers.TestSetup();
2223
testSetup.lockingToken = await ERC20Mock.new(accounts[0], web3.utils.toWei('100', "ether"));
2324
var controllerCreator = await ControllerCreator.new({gas: constants.ARC_GAS_LIMIT});
24-
testSetup.daoCreator = await DaoCreator.new(controllerCreator.address,{gas:constants.ARC_GAS_LIMIT});
25+
var daoTracker = await DAOTracker.new({gas: constants.ARC_GAS_LIMIT});
26+
testSetup.daoCreator = await DaoCreator.new(controllerCreator.address,daoTracker.address,{gas:constants.ARC_GAS_LIMIT});
2527

2628
testSetup.org = await helpers.setupOrganization(testSetup.daoCreator,accounts[0],1000,1000);
2729
testSetup.startTime = (await web3.eth.getBlock("latest")).timestamp + _startTime;

test/contributionreward.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ const ContributionReward = artifacts.require("./ContributionReward.sol");
44
const ERC20Mock = artifacts.require('./test/ERC20Mock.sol');
55
const DaoCreator = artifacts.require("./DaoCreator.sol");
66
const ControllerCreator = artifacts.require("./ControllerCreator.sol");
7+
const DAOTracker = artifacts.require("./DAOTracker.sol");
78
const Avatar = artifacts.require("./Avatar.sol");
89
const Redeemer = artifacts.require("./Redeemer.sol");
910

@@ -75,7 +76,8 @@ const setup = async function (accounts,genesisProtocol = false,tokenAddress=0) {
7576
testSetup.standardTokenMock = await ERC20Mock.new(accounts[1],100);
7677
testSetup.contributionReward = await ContributionReward.new();
7778
var controllerCreator = await ControllerCreator.new({gas: constants.ARC_GAS_LIMIT});
78-
testSetup.daoCreator = await DaoCreator.new(controllerCreator.address,{gas:constants.ARC_GAS_LIMIT});
79+
var daoTracker = await DAOTracker.new({gas: constants.ARC_GAS_LIMIT});
80+
testSetup.daoCreator = await DaoCreator.new(controllerCreator.address,daoTracker.address,{gas:constants.ARC_GAS_LIMIT});
7981
if (genesisProtocol) {
8082
testSetup.reputationArray = [1000,100,0];
8183
} else {

test/daocreator.js

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ const UController = artifacts.require("./UController.sol");
99
const ERC20Mock = artifacts.require('./test/ERC20Mock.sol');
1010
const UniversalSchemeMock = artifacts.require('./test/UniversalSchemeMock.sol');
1111
const ControllerCreator = artifacts.require("./ControllerCreator.sol");
12+
const DAOTracker = artifacts.require("./DAOTracker.sol");
1213

1314
const zeroBytes32 = helpers.NULL_HASH;
1415
var avatar,token,reputation,daoCreator,uController,controllerCreator;
1516
const setup = async function (accounts,founderToken,founderReputation,useUController=false,cap=0) {
1617
controllerCreator = await ControllerCreator.new({gas: constants.ARC_GAS_LIMIT});
17-
daoCreator = await DaoCreator.new(controllerCreator.address,{gas:constants.ARC_GAS_LIMIT});
18+
var daoTracker = await DAOTracker.new({gas: constants.ARC_GAS_LIMIT});
19+
daoCreator = await DaoCreator.new(controllerCreator.address,daoTracker.address,{gas:constants.ARC_GAS_LIMIT});
1820
var uControllerAddress = helpers.NULL_ADDRESS;
1921
if (useUController){
2022
uController = await UController.new({gas:constants.ARC_GAS_LIMIT});
@@ -289,7 +291,8 @@ contract('DaoCreator', function(accounts) {
289291
it("forgeOrg with different params length should revert", async function() {
290292
var amountToMint = 10;
291293
var controllerCreator = await ControllerCreator.new({gas: constants.ARC_GAS_LIMIT});
292-
daoCreator = await DaoCreator.new(controllerCreator.address,{gas:constants.ARC_GAS_LIMIT});
294+
var daoTracker = await DAOTracker.new({gas: constants.ARC_GAS_LIMIT});
295+
daoCreator = await DaoCreator.new(controllerCreator.address,daoTracker.address,{gas:constants.ARC_GAS_LIMIT});
293296
var uControllerAddress = helpers.NULL_ADDRESS;
294297
try {
295298
await daoCreator.forgeOrg("testOrg","TEST","TST",[accounts[0]],[amountToMint],[],uControllerAddress,helpers.NULL_ADDRESS,{gas:constants.ARC_GAS_LIMIT});

0 commit comments

Comments
 (0)