Skip to content

Commit

Permalink
Survey: Add example survey kit (#915)
Browse files Browse the repository at this point in the history
  • Loading branch information
facuspagnuolo committed Sep 3, 2019
1 parent 3c29762 commit d406ab0
Show file tree
Hide file tree
Showing 6 changed files with 509 additions and 2 deletions.
6 changes: 4 additions & 2 deletions apps/survey/.solcover.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
module.exports = {
norpc: true,
copyPackages: ['@aragon/os', '@aragon/apps-shared-minime', '@aragon/test-helpers'],
copyPackages: ['@aragon/os', '@aragon/apps-shared-minime', '@aragon/test-helpers', '@aragon/apps-vault'],
skipFiles: [
'test',
'examples',
'@aragon/os',
'@aragon/apps-shared-minime',
'@aragon/apps-vault',
'@aragon/test-helpers',
'@aragon/apps-shared-minime',
],
// Turn on deep skip to avoid preprocessing (e.g. removing view/pure modifiers) for skipped files
deepSkip: true
Expand Down
103 changes: 103 additions & 0 deletions apps/survey/contracts/examples/SurveyKit.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
pragma solidity 0.4.24;

import "@aragon/apps-vault/contracts/Vault.sol";
import "@aragon/os/contracts/acl/ACL.sol";
import "@aragon/os/contracts/kernel/Kernel.sol";
import "@aragon/os/contracts/kernel/KernelConstants.sol";
import "@aragon/os/contracts/apm/Repo.sol";
import "@aragon/os/contracts/apm/APMNamehash.sol";
import "@aragon/os/contracts/factory/DAOFactory.sol";
import "@aragon/os/contracts/lib/ens/ENS.sol";
import "@aragon/os/contracts/lib/ens/PublicResolver.sol";
import "@aragon/apps-shared-minime/contracts/MiniMeToken.sol";

import "../Survey.sol";


contract KitBase is APMNamehash, EVMScriptRegistryConstants {
ENS public ens;
DAOFactory public fac;

event DeployInstance(address dao);
event InstalledApp(address appProxy, bytes32 appId);

constructor(DAOFactory _fac, ENS _ens) public {
ens = _ens;

// If no factory is passed, get it from on-chain bare-kit
if (address(_fac) == address(0)) {
bytes32 bareKit = apmNamehash("bare-kit");
fac = KitBase(latestVersionAppBase(bareKit)).fac();
} else {
fac = _fac;
}
}

function latestVersionAppBase(bytes32 appId) public view returns (address base) {
Repo repo = Repo(PublicResolver(ens.resolver(appId)).addr(appId));
(,base,) = repo.getLatest();

return base;
}

function cleanupDAOPermissions(Kernel dao, ACL acl, address root) internal {
// Kernel permission clean up
cleanupPermission(acl, root, dao, dao.APP_MANAGER_ROLE());

// ACL permission clean up
cleanupPermission(acl, root, acl, acl.CREATE_PERMISSIONS_ROLE());
}

function cleanupPermission(ACL acl, address root, address app, bytes32 permission) internal {
acl.grantPermission(root, app, permission);
acl.revokePermission(this, app, permission);
acl.setPermissionManager(root, app, permission);
}
}

contract SurveyKit is KernelAppIds, KitBase {
// bytes32 constant public SURVEY_APP_ID = apmNamehash("survey");
bytes32 constant public SURVEY_APP_ID = 0x030b2ab880b88e228f2da5a3d19a2a31bc10dbf91fb1143776a6de489389471e; // survey.aragonpm.eth

event DeployInstance(address dao, address indexed token);

constructor(DAOFactory _fac, ENS _ens) KitBase(_fac, _ens) public {
// factory must be set up w/o EVMScript support
require(address(_fac.regFactory()) == address(0));
}

function newInstance(
MiniMeToken signalingToken,
address surveyManager,
address escapeHatchOwner,
uint64 duration,
uint64 participation
)
public
returns (Kernel, Survey)
{
Kernel dao = fac.newDAO(this);
ACL acl = ACL(dao.acl());
acl.createPermission(this, dao, dao.APP_MANAGER_ROLE(), this);

// Install survey app
Survey survey = Survey(dao.newAppInstance(SURVEY_APP_ID, latestVersionAppBase(SURVEY_APP_ID)));
survey.initialize(signalingToken, participation, duration);
emit InstalledApp(survey, SURVEY_APP_ID);

// Install a vault to let it be the escape hatch
Vault vault = Vault(dao.newAppInstance(KERNEL_DEFAULT_VAULT_APP_ID, latestVersionAppBase(KERNEL_DEFAULT_VAULT_APP_ID)));
vault.initialize();
emit InstalledApp(vault, KERNEL_DEFAULT_VAULT_APP_ID);

// Set survey manager as the entity that can create votes and change participation
// surveyManager can then give this permission to other entities
acl.createPermission(surveyManager, survey, survey.CREATE_SURVEYS_ROLE(), surveyManager);
acl.createPermission(surveyManager, survey, survey.MODIFY_PARTICIPATION_ROLE(), surveyManager);
acl.createPermission(escapeHatchOwner, vault, vault.TRANSFER_ROLE(), escapeHatchOwner);
cleanupDAOPermissions(dao, acl, surveyManager);

emit DeployInstance(dao, signalingToken);
return (dao, survey);
}
}
1 change: 1 addition & 0 deletions apps/survey/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
"license": "(GPL-3.0-or-later OR AGPL-3.0-or-later)",
"description": "",
"devDependencies": {
"@aragon/apps-vault": "^4.0.0",
"@aragon/apps-shared-migrations": "1.0.0",
"@aragon/apps-shared-scripts": "^1.0.0",
"@aragon/cli": "^6.0.0",
Expand Down
238 changes: 238 additions & 0 deletions apps/survey/scripts/demo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,238 @@
const namehash = require('eth-ens-namehash').hash

const ENS = artifacts.require('@aragon/os/contracts/lib/ens/ENS')
const MiniMeToken = artifacts.require('@aragon/os/contracts/lib/minime/MiniMeToken')
const MiniMeTokenFactory = artifacts.require('@aragon/os/contracts/lib/minime/MiniMeTokenFactory')
const PublicResolver = artifacts.require('@aragon/os/contracts/lib/ens/PublicResolver')
const Repo = artifacts.require('@aragon/os/contracts/apm/Repo')

const SurveyKit = artifacts.require('@aragon/kits-survey/contracts/SurveyKit')
const Survey = artifacts.require('@aragon/apps-survey/contracts/Survey')

// Utils
const surveyAppId = namehash('survey.aragonpm.eth')
const surveyKitEnsNode = namehash('survey-kit.aragonpm.eth')
const pct16 = x => new web3.BigNumber(x).times(new web3.BigNumber(10).toPower(16))
const createdSurveyDao = receipt => receipt.logs.filter(x => x.event === 'DeployInstance')[0].args.dao
const installedApp = (receipt, appId) => receipt.logs.filter(x => x.event === 'InstalledApp' && x.args.appId === appId)[0].args.appProxy

// Accounts (not held by root)
const accounts = [
'0x8401Eb5ff34cc943f096A32EF3d5113FEbE8D4Eb',
'0x306469457266CBBe7c0505e8Aad358622235e768',
'0xd873F6DC68e3057e4B7da74c6b304d0eF0B484C7',
'0xDcC5dD922fb1D0fd0c450a0636a8cE827521f0eD',
]

// Survey params
const TOKEN_BASE_DECIMAL = 1e18
const SURVEY_CHART_BLOCKS = 16 // front-end chart blocks
const SURVEY_DURATION = 60 * 60 * SURVEY_CHART_BLOCKS // one hour for each block
const SURVEY_PARTICIPATION = pct16(10) // 10%

module.exports = async () => {
console.log('Launching Survey demo')

if (process.argv.length < 5 || process.argv[3] !== '--network') {
console.error('Usage: truffle exec --network <network> scripts/launch-demo.js')
exit(1)
}
const owner = process.env.OWNER
const ens = ENS.at(
process.env.ENS ||
'0x5f6f7e8cc7346a11ca2def8f827b7a0b612c56a1' // aragen's default ENS
)

console.log('ENS:', ens.address)
console.log('Owner:', owner)

const resolver = PublicResolver.at(await ens.resolver(surveyKitEnsNode))
const surveyKitRepo = Repo.at(await resolver.addr(surveyKitEnsNode))

// Contract address is second return of Repo.getLatest()
const surveyKit = SurveyKit.at((await surveyKitRepo.getLatest())[1])
console.log('SurveyKit:', surveyKit.address)

// Create minime token and assign to accounts
const minimeFac = await MiniMeTokenFactory.new()
const surveyToken = await MiniMeToken.new(minimeFac.address, '0x00', '0x00', 'Demo Survey', 18, 'SUR', true)

// Total of 40 tokens being assigned (10 to owner)
const tokenAssignments = [
TOKEN_BASE_DECIMAL * 16,
TOKEN_BASE_DECIMAL * 8,
TOKEN_BASE_DECIMAL * 4,
TOKEN_BASE_DECIMAL * 2,
TOKEN_BASE_DECIMAL * 10, // owner
]
await Promise.all(
tokenAssignments.map(async (amount, i) => {
let account
if (i < accounts.length) {
account = accounts[i]
} else {
account = owner
}

await surveyToken.generateTokens(account, amount)
console.log(`Assigned ${account} ${amount} tokens`)
})
)

// Create DAO with just Survey installed
console.log('Creating Survey DAO...')
const surveyDaoReceipt = await surveyKit.newInstance(surveyToken.address, owner, owner, SURVEY_DURATION, SURVEY_PARTICIPATION)
const surveyDaoAddr = createdSurveyDao(surveyDaoReceipt)
const surveyAppAddr = installedApp(surveyDaoReceipt, surveyAppId)

// Create some sample surveys and vote on them
const survey = Survey.at(surveyAppAddr)

const newSurvey = ({ question, description, options, url }) => {
const metadata = {
specId: namehash('1.metadata.survey.aragonpm.eth'),
metadata: {
question,
description,
options,
url,
}
}
return survey.newSurvey(JSON.stringify(metadata), options.length)
}
const voteSurvey = (surveyId, { options, stakes }, txOptions) => {
return survey.voteOptions(surveyId, options, stakes, txOptions)
}

// Start some surveys
console.log('Creating some surveys...')
const surveyMetadata = [
{
question: 'Should we get Carlos Matos?',
description: 'Coming to an event near you!',
options: ['OMG YES', ':sigh:', "You're telling me, he's real???"],
url: 'https://github.com/aragon/nest/issues/4',
},
{
question: 'When should the Aragon Network be launched?',
description: 'The Aragon Network is an upcoming launch by the Aragon team!',
options: ['Q1 2019', 'Q2 2019', 'Q3 2019', 'Q4 2019', 'Soon'],
url: 'https://github.com/aragon/nest/issues/1',
},
{
question: 'What should we name our 0.6 release?',
description: 'We want your help in naming it!',
options: ['Buidler', 'Engineer', 'Artist', 'Infinity'],
url: 'https://github.com/aragon/nest/issues/3',
},
{
question: 'Should ANT be redeployed as an ERC-777 token?',
description: 'The current ANT token is a MiniMe token, but this is costly in gas for most users.',
options: ['Yes', 'No'],
url: 'https://github.com/aragon/nest/issues/2',
},
]
const surveys = await Promise.all(
surveyMetadata.map(
metadata => newSurvey(metadata)
.then(receipt => ({
metadata,
id: receipt.logs.filter(x => x.event === 'StartSurvey')[0].args.surveyId,
}))
)
)

// Vote on the surveys—each account will vote for a few options at a specific
// point in time (to make them show up in different time buckets in the UI)
// REMEMBER: optionIds start from 1, not 0!
console.log('Voting on the surveys...')
const account1Votes = [
{
options: [2],
stakes: [TOKEN_BASE_DECIMAL * 2],
},
{
options: [5],
stakes: [TOKEN_BASE_DECIMAL * 5],
},
{
options: [1, 3],
stakes: [TOKEN_BASE_DECIMAL * 8, TOKEN_BASE_DECIMAL * 3],
},
]
await Promise.all(
account1Votes.map(
(voteParams, index) => voteSurvey(surveys[index].id, voteParams, { from: accounts[0] })
)
)
// Haha, this time travel idea doesn't actually work
// await timeTravel(SURVEY_DURATION / SURVEY_CHART_BLOCKS + 1)

const account2Votes = [
{
options: [1],
stakes: [TOKEN_BASE_DECIMAL * 1],
},
{
options: [1, 2],
stakes: [TOKEN_BASE_DECIMAL * 1, TOKEN_BASE_DECIMAL * 1],
},
{
options: [2],
stakes: [TOKEN_BASE_DECIMAL * 1],
},
]
await Promise.all(
account2Votes.map(
(voteParams, index) => voteSurvey(surveys[index].id, voteParams, { from: accounts[1] })
)
)
// await timeTravel(SURVEY_DURATION / SURVEY_CHART_BLOCKS + 1)

const account3Votes = [
{
options: [2],
stakes: [TOKEN_BASE_DECIMAL * 4],
},
{
options: [3, 4],
stakes: [TOKEN_BASE_DECIMAL * 2, TOKEN_BASE_DECIMAL * 1],
},
{
options: [2],
stakes: [TOKEN_BASE_DECIMAL * 1],
},
]
await Promise.all(
account3Votes.map(
(voteParams, index) => voteSurvey(surveys[index].id, voteParams, { from: accounts[2] })
)
)
// await timeTravel(SURVEY_DURATION / SURVEY_CHART_BLOCKS + 1)

const account4Votes = [
{
options: [1],
stakes: [TOKEN_BASE_DECIMAL * 1],
},
{
options: [1],
stakes: [TOKEN_BASE_DECIMAL * 1],
},
{
options: [1],
stakes: [TOKEN_BASE_DECIMAL * 1],
},
]
await Promise.all(
account4Votes.map(
(voteParams, index) => voteSurvey(surveys[index].id, voteParams, { from: accounts[3] })
)
)

console.log('===========')
console.log('Survey demo DAO set up!')
console.log('Survey DAO:', surveyDaoAddr)
console.log("Survey DAO's Survey app:", surveyAppAddr)
console.log('Survey Token:', surveyToken.address)
}
Loading

0 comments on commit d406ab0

Please sign in to comment.