Merge pull request #213 from 67P/hardhat

Hardhat
This commit is contained in:
Basti 2022-07-14 15:31:48 +02:00 committed by GitHub
commit 683ec5b2e9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
120 changed files with 31659 additions and 47213 deletions

6
.gitignore vendored
View File

@ -1,8 +1,10 @@
build
flattened_contracts
node_modules
**/node_modules
.ganache-db
.tm_properties
yarn-error.log
.DS_Store
cache
artifacts
.openzeppelin

2
.nvmrc
View File

@ -1 +1 @@
12
16

207
README.md
View File

@ -2,13 +2,8 @@
# Kredits Contracts
This repository contains the Solidity smart contracts organized as
[Aragon](https://hack.aragon.org/) apps and JavaScript API wrapper for [Kosmos
Kredits](https://wiki.kosmos.org/Kredits).
It is based on [aragonOS](https://hack.aragon.org/docs/aragonos-intro.html) and
follows the aragonOS conventions. Aragon itself uses the [Truffle
framework](http://truffleframework.com/) for some things.
This repository contains the Solidity smart contracts and the JavaScript API
wrapper for [Kosmos Kredits](https://wiki.kosmos.org/Kredits).
## Development
@ -20,224 +15,108 @@ All requirements are defined in `package.json`.
$ npm install
Each of the aragon apps are separate packages:
$ cd apps/[app]
$ npm install
You can use `npm run install-all` to install all app dependencies at once.
#### Sytem dependencies
Aragon CLI and Truffle need to be installed on your sytem as well:
npm install -g @aragon/cli
npm install -g truffle
_Note: `@aragon/cli` currently fails to install on node.js 14. Please use
node.js 12 until the issue has been resolved upstream._
### Local development chain
For local development it is recommended to use
[ganache](http://truffleframework.com/ganache/) to run a local development
chain. When using the ganache simulator, no full Ethereum node is required.
We use [hardhat](https://hardhat.org/) as development environment for the
smart contracts.
We use the default aragon-cli devchain command to configure and run a local
development ganache.
To run a local development chain run:
$ npm run devchain (or aragon devchain --port 7545)
To clear/reset the chain use (e.g. if you run out of funds on your devchain)
$ npm run devchain -- --reset (or aragon devchain --port 7545 --reset)
We default to port 7545 for development to not get in conflict with the default
Ethereum RPC port.
You can also set certain ganache options to configure the devchain, for example
if you want to increase the block time to 10 seconds you can add
`--block-time=10`.
$ npm run devchain # or: hardhat node --network hardhat
### Bootstrap
1. Run an Ethereum node and ipfs
1. Run an Ethereum node and ipfs
$ npm run devchain
$ ipfs daemon
2. Compile contracts
2. Compile contracts and build ABIs
(compiled contracts will be in `/build`)
$ npm run compile-contracts
(compiled artifacts will be in `/artifacts`)
$ npm run build
3. Deploy each app to the devchain
3. Deploy new upgradable contract proxies
(make sure you've run `npm install` for every app - see installation)
$ npm run deploy:apps
4. Deploy a new KreditsKit and create a new DAO with the latest app versions
$ npm run deploy:kit
$ npm run deploy:dao
5. Execute seeds to create demo contributors, contributions, etc. (optional)
4. Execute seeds to create demo contributors, contributions, etc. (optional)
$ npm run seeds
**Step 2-5 is also summarized in `npm run bootstrap`**
**Step 2-4 is also summarized in `npm run bootstrap`**
If you want to reset your local setup:
5. Show contract addresses
$ npm run reset // deploys a new kit and a new DAO
$ npm run reset:hard // deploys all apps and does reset
$ cat lib/addresses.json
## Fund a local development account
If you need to fund development accounts with devchain coins:
$ npm run fund # or hardhat fund --network localhost
## Contract architecture
Contracts are organized in independent apps (see `/apps`) and are developed and
deployed independently. Each app has a version and can be "installed" on the
Kredits DAO independently.
![](docs/kredits-diagram.png)
A DAO can be deployed using the `scripts/deploy-kit.js` script or with the
`npm run deploy:dao` command. This deploys a new Kredits DAO, installs the
latest app versions and sets the required permissions.
See each app in `/apps/*` for details.
We use the [OpenZeppelin hardhat
proxy](https://www.npmjs.com/package/@openzeppelin/hardhat-upgrades) for
deploying and managing upgradeable contracts. (see `scripts/create-proxy.js`)
Each contract is independent and is connected to its dependencies by storing
the addresses of the other contracts.
## Helper scripts
`scripts/` contains some helper scripts to interact with the contracts from the
CLI. _At some point these should be moved into a real nice CLI._
To run these scripts use `truffle exec`. For example: `truffle exec
scripts/add-proposal.js`.
To run these scripts use `hardhat run`. For example: `hardhat run
scripts/list-contributors.js --network localhost`. (NOTE: add `--network
localhost` or the network you want to use)
Some scripts are also defined as npm script, see package.json.
Some scripts are also defined as npm script, see `package.json`.
### cli.js
Call any function on any contract:
$ truffle exec scripts/cli.js
### repl.js
### repl/console
Similar to cli.js but only provides a REPL with an initialized `kredits`
instance.
$ truffle exec scripts/repl.js
$ hardhat console --network localhost
### add-{contributor, contribution, proposal}.js
Script to add a new entries to the contracts using the JS wrapper
$ truffle exec scripts/add-{contributor, contribution, proposal}.js
$ hardhat run scripts/add-{contributor, contribution, proposal}.js --network localhost
### list-{contributors, contributions, proposals}.js
List contract entries
$ truffle exec scripts/list-{contributors, contributions, proposals}.js
### send-funds.js
Sends funds to an address. Helpful in development mode to for example fund a
metamask account.
$ truffle exec scripts/send-funds.js
$ hardhat run scripts/list-{contributors, contributions, proposals}.js --network localhost
### seeds.js
Run seeds defined in `config/seeds.js`.
$ truffle exec scripts/seeds.js
or
$ npm run seeds
### current-address.js
### Get the contract addresses
Prints all known DAO addresses and the DAO address for the current network
All contract addresses are stored in `lib/addresses.json`
$ truffle exec scripts/current-address.js
or
$ npm run dao:address
### deploy-kit.js
Deploys a new KreditsKit that allows to create a new DAO
$ truffle exec script/deploy-kit.js
or
$ npm run deploy:kit
#### Kredits configuration options:
Configuration options can be set in an environment specific `kredits` object in the `arapp.json` or using a CLI parameter.
* daoFactory: Ethereum address of the used DAO Factory. On public networks we use [official aragon factories](https://github.com/aragon/deployments/tree/master/environments/)
* apmDomain: the ENS domain of the aragonPM (normally `open.aragonpm.eth`)
(please also see the [arapp.json related configuration options](https://hack.aragon.org/docs/cli-global-confg#the-arappjson-file))
### new-dao.js
Creates and configures a new DAO instance.
$ truffle exec script/new-dao.js
or
$ npm run deploy:dao
KreditsKit address is loaded from `lib/addresses/KreditsKit.json` or can be
configured through the `KREDITS_KIT` environment variable.
### deploy-apps.sh
Runs `npm install` for each app and publishes a new version.
$ ./scripts/deploy-apps.sh
or
$ npm run deploy:apps
## Deployment
### Apps deployment
To deploy a new app version run:
$ aragon apm publish major --environment=NETWORK_TO_DEPLOY
### KreditsKit
deploy the KreditsKit as Kit to create new DAOs
$ truffle exec scripts/deploy-kit.js --network=NETWORK_TO_DEPLOY
### Creating a new DAO
make sure all apps and the KreditsKit are deployed, then create a new DAO:
$ truffle exec scripts/new-dao.js --network=NETWORK_TO_DEPLOY
## ACL / Permissions
$ cat lib/addresses.json
## Upgradeable contracts
We use aragonOS for upgradeability of the different contracts. Refer to the
[aragonOS upgradeablity documentation](https://hack.aragon.org/docs/upgradeability-intro)
for more details.
We use OpenZeppelin for an upgradeable contracts:
[https://www.npmjs.com/package/@openzeppelin/hardhat-upgrades](https://www.npmjs.com/package/@openzeppelin/hardhat-upgrades)
### Example
Refer to the OpenZeppelin README and `scripts/create-proxy.js`
1. Setup (see #Bootstrap)
1. Deploy each contract/apps (see `/apps/*`)
2. Create a new DAO (see scripts/deploy-kit.js)
2. Update
1. Deploy a new Version of the contract/app (see `/apps/*`)
2. Use the `aragon dao upgrade` command to "install" the new version for the DAO
(`aragon dao upgrade <DAO address> <app name>`)
[OpenZeppelin Step by Step guide](https://forum.openzeppelin.com/t/openzeppelin-upgrades-step-by-step-tutorial-for-hardhat/3580)
For an upgrade example checkout `scripts/upgrade-example.js`
## Known Issues

View File

@ -1,12 +0,0 @@
module.exports = {
'globals': {
contract: true,
describe: true,
it: true,
},
rules: {
'no-unused-vars': ['error', {
'argsIgnorePattern': '^_',
}],
}
}

View File

@ -1,4 +0,0 @@
node_modules
build
.cache
dist

View File

@ -1,14 +0,0 @@
# Git files
.gitignore
# Build files
.cache
node_modules
build
# Lock files
package-lock.json
yarn.lock
# Others
test

View File

@ -1 +0,0 @@
# Kredits Contribution

View File

@ -1,38 +0,0 @@
{
"roles": [
{
"name": "Add contributions",
"id": "ADD_CONTRIBUTION_ROLE",
"params": []
},
{
"name": "Manage token contract",
"id": "MANAGE_TOKEN_CONTRACT_ROLE",
"params": []
},
{
"name": "Veto contributions",
"id": "VETO_CONTRIBUTION_ROLE",
"params": []
}
],
"environments": {
"default": {
"network": "development",
"appName": "kredits-contribution.open.aragonpm.eth"
},
"rinkeby": {
"registry": "0x98df287b6c145399aaa709692c8d308357bc085d",
"appName": "kredits-contribution.open.aragonpm.eth",
"wsRPC": "wss://rinkeby.eth.aragon.network/ws",
"network": "rinkeby"
},
"production": {
"registry": "0x314159265dd8dbb310642f98f50c066173c1259b",
"appName": "contribution.open.aragonpm.eth",
"wsRPC": "wss://mainnet.eth.aragon.network/ws",
"network": "mainnet"
}
},
"path": "contracts/Contribution.sol"
}

View File

@ -1,23 +0,0 @@
pragma solidity ^0.4.24;
contract Migrations {
address public owner;
uint public last_completed_migration;
modifier restricted() {
if (msg.sender == owner) _;
}
constructor() public {
owner = msg.sender;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

View File

@ -1,4 +0,0 @@
{
"name": "Contribution",
"description": "Kredits contribution app"
}

View File

@ -1,5 +0,0 @@
var Migrations = artifacts.require('./Migrations.sol')
module.exports = function (deployer) {
deployer.deploy(Migrations)
}

View File

@ -1,5 +0,0 @@
var Contribution = artifacts.require('Contribution.sol')
module.exports = function (deployer) {
deployer.deploy(Contribution)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +0,0 @@
{
"name": "kredits-contribution",
"version": "1.0.0",
"description": "",
"dependencies": {
"@aragon/os": "^4.4.0"
},
"devDependencies": {
"@aragon/test-helpers": "^2.1.0",
"eth-gas-reporter": "^0.2.17",
"ganache-cli": "^6.9.1",
"solidity-coverage": "^0.5.11"
},
"scripts": {
"start": "npm run start:aragon:ipfs",
"start:aragon:ipfs": "aragon run",
"start:aragon:http": "aragon run --http localhost:8001 --http-served-from ./dist",
"start:app": "",
"compile": "aragon contracts compile",
"sync-assets": "",
"build:app": "",
"build:script": "",
"build": "",
"publish:patch": "aragon apm publish patch",
"publish:minor": "aragon apm publish minor",
"publish:major": "aragon apm publish major",
"versions": "aragon apm versions",
"test": "truffle test"
},
"keywords": []
}

View File

@ -1,5 +0,0 @@
// const Contribution = artifacts.require('Contribution.sol');
contract('Contribution', (_accounts) => {
it('should be tested');
});

View File

@ -1 +0,0 @@
module.exports = require("../../truffle.js");

View File

@ -1,4 +0,0 @@
node_modules
build
.cache
dist

View File

@ -1,14 +0,0 @@
# Git files
.gitignore
# Build files
.cache
node_modules
build
# Lock files
package-lock.json
yarn.lock
# Others
test

View File

@ -1 +0,0 @@
# Kredits Contributor

View File

@ -1,28 +0,0 @@
{
"roles": [
{
"name": "Manage contributors",
"id": "MANAGE_CONTRIBUTORS_ROLE",
"params": []
}
],
"environments": {
"default": {
"network": "development",
"appName": "kredits-contributor.open.aragonpm.eth"
},
"rinkeby": {
"registry": "0x98df287b6c145399aaa709692c8d308357bc085d",
"appName": "kredits-contributor.open.aragonpm.eth",
"wsRPC": "wss://rinkeby.eth.aragon.network/ws",
"network": "rinkeby"
},
"production": {
"registry": "0x314159265dd8dbb310642f98f50c066173c1259b",
"appName": "contributor.open.aragonpm.eth",
"wsRPC": "wss://mainnet.eth.aragon.network/ws",
"network": "mainnet"
}
},
"path": "contracts/Contributor.sol"
}

View File

@ -1,23 +0,0 @@
pragma solidity ^0.4.24;
contract Migrations {
address public owner;
uint public last_completed_migration;
modifier restricted() {
if (msg.sender == owner) _;
}
constructor() public {
owner = msg.sender;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

View File

@ -1,16 +0,0 @@
pragma solidity ^0.4.24;
import "@aragon/os/contracts/acl/ACL.sol";
import "@aragon/os/contracts/kernel/Kernel.sol";
import "@aragon/os/contracts/factory/DAOFactory.sol";
// You might think this file is a bit odd, but let me explain.
// We only use for now those imported contracts in our tests, which
// means Truffle will not compile them for us, because they are from
// an external dependency.
// solium-disable-next-line no-empty-blocks
contract Spoof {
// ...
}

View File

@ -1,4 +0,0 @@
{
"name": "Contributor",
"description": "Kredits Contributor app"
}

View File

@ -1,5 +0,0 @@
var Migrations = artifacts.require('./Migrations.sol')
module.exports = function (deployer) {
deployer.deploy(Migrations)
}

View File

@ -1,5 +0,0 @@
var Contributor = artifacts.require('Contributor.sol')
module.exports = function (deployer) {
deployer.deploy(Contributor)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +0,0 @@
{
"name": "kredits-contributor",
"version": "1.0.0",
"description": "",
"dependencies": {
"@aragon/os": "^4.4.0"
},
"devDependencies": {
"@aragon/test-helpers": "^2.1.0",
"eth-gas-reporter": "^0.2.17",
"ganache-cli": "^6.9.1",
"solidity-coverage": "^0.5.11"
},
"scripts": {
"start": "npm run start:aragon:ipfs",
"start:aragon:ipfs": "aragon run",
"start:aragon:http": "aragon run --http localhost:8001 --http-served-from ./dist",
"start:app": "",
"compile": "aragon contracts compile",
"sync-assets": "",
"build:app": "",
"build:script": "",
"build": "",
"publish:patch": "aragon apm publish patch",
"publish:minor": "aragon apm publish minor",
"publish:major": "aragon apm publish major",
"versions": "aragon apm versions",
"test": "truffle test"
},
"keywords": []
}

View File

@ -1,170 +0,0 @@
const namehash = require('ethers').utils.namehash;
// eslint-disable-next-line no-undef
const Contributor = artifacts.require("Contributor.sol");
// eslint-disable-next-line no-undef
const getContract = name => artifacts.require(name);
const { assertRevert } = require('@aragon/test-helpers/assertThrow');
const ZERO_ADDR = '0x0000000000000000000000000000000000000000';
contract('Contributor app', (accounts) => {
let kernelBase, aclBase, daoFactory, r, dao, acl, contributor;
const root = accounts[0];
const member1 = accounts[1];
// eslint-disable-next-line no-undef
before(async () => {
kernelBase = await getContract('Kernel').new(true); // petrify immediately
aclBase = await getContract('ACL').new();
daoFactory = await getContract('DAOFactory').new(kernelBase.address, aclBase.address, ZERO_ADDR);
r = await daoFactory.newDAO(root);
dao = await getContract('Kernel').at(r.logs.filter(l => l.event == 'DeployDAO')[0].args.dao);
acl = await getContract('ACL').at(await dao.acl());
//create dao mamnager permission for coin owner
await acl.createPermission(
root,
dao.address,
await dao.APP_MANAGER_ROLE(),
root,
{ from: root }
);
//get new app instance from DAO
const receipt = await dao.newAppInstance(
'0x1234',
(await Contributor.new()).address,
0x0,
false,
{ from: root }
);
contributor = Contributor.at(
receipt.logs.filter(l => l.event == 'NewAppProxy')[0].args.proxy
);
//apps id
let appsId = [];
appsId[0] = namehash("kredits-contribution");
appsId[1] = namehash("kredits-contributor");
appsId[2] = namehash("kredits-proposal");
appsId[3] = namehash("kredits-token");
//init contributor (app)
await contributor.initialize(root, appsId);
//create manage contributors role
await acl.createPermission(
root,
contributor.address,
await contributor.MANAGE_CONTRIBUTORS_ROLE(),
root,
{ from: root }
);
});
describe("Owner default permissions", async () => {
it('check owner is contributors manager', async () => {
let manageContributorPermission = await acl.hasPermission(root, contributor.address, await contributor.MANAGE_CONTRIBUTORS_ROLE());
// eslint-disable-next-line no-undef
assert.equal(manageContributorPermission, true);
});
});
describe("Add contributor", async () => {
let account = root;
let hashDigest = '0x0';
let hashFunction = 0;
let hashSize = 0;
it("should revert when add contributor from an address that does not have permission", async () => {
return assertRevert(async () => {
await contributor.addContributor(account, hashDigest, hashFunction, hashSize, { from: member1});
'sender does not have permission';
});
});
it('add contributor', async () => {
let contributorCount = await contributor.coreContributorsCount();
await contributor.addContributor(account, hashDigest, hashFunction, hashSize);
// eslint-disable-next-line no-undef
assert.equal(await contributor.addressExists(account), true);
let contributorCountAfter = await contributor.coreContributorsCount();
// eslint-disable-next-line no-undef
assert.equal(await contributorCountAfter.toNumber(), parseInt(contributorCount)+1);
});
it("should revert when add contributor with an address that already exist", async () => {
return assertRevert(async () => {
await contributor.addContributor(account, hashDigest, hashFunction, hashSize, { from: member1});
'address already exist';
});
});
});
describe("Update contributor", async () => {
let id;
let oldAccount;
let newAccount;
let hashDigest;
let hashFunction;
let hashSize;
// eslint-disable-next-line no-undef
before(async () => {
id = await contributor.coreContributorsCount();
oldAccount = root;
newAccount = member1;
hashDigest = '0x1000000000000000000000000000000000000000000000000000000000000000';
hashFunction = 1;
hashSize = 1;
});
it('update contributor account', async () => {
await contributor.updateContributorAccount(id.toNumber(), oldAccount, newAccount);
let contributorId = await contributor.getContributorIdByAddress(oldAccount);
// eslint-disable-next-line no-undef
assert.equal(contributorId.toNumber(), 0);
});
it("should revert when update contributor account from address that does not have permission", async () => {
return assertRevert(async () => {
await contributor.updateContributorAccount(id.toNumber(), oldAccount, newAccount, {from: member1});
'sender does not have permission';
});
});
it("should revert when update contributor account that does not exist", async () => {
return assertRevert(async () => {
await contributor.updateContributorAccount(id.toNumber(), accounts[3], newAccount);
'contributor does not exist';
});
});
it("should revert when update contributor account with address(0)", async () => {
return assertRevert(async () => {
await contributor.updateContributorAccount(id.toNumber(), oldAccount, ZERO_ADDR);
'contributor does not exist';
});
});
it('update contributor profile hash', async () => {
await contributor.updateContributorProfileHash(id.toNumber(), hashDigest, hashFunction, hashSize);
let contributorProfile = await contributor.contributors(id.toNumber());
assert.equal(hashDigest, contributorProfile[1]); // eslint-disable-line no-undef
assert.equal(hashFunction, contributorProfile[2].toNumber()); // eslint-disable-line no-undef
assert.equal(hashSize, contributorProfile[3].toNumber()); // eslint-disable-line no-undef
});
it("should revert when update contributor profile hash from address that does not have permission", async () => {
return assertRevert(async () => {
await contributor.updateContributorProfileHash(id.toNumber(), hashDigest, hashFunction, hashSize, {from: member1});
'sender does not have permission';
});
});
});
});

View File

@ -1 +0,0 @@
module.exports = require("../../truffle.js");

View File

@ -1 +0,0 @@
# Kredits Proposal

View File

@ -1,33 +0,0 @@
{
"roles": [
{
"name": "Add proposal",
"id": "ADD_PROPOSAL_ROLE",
"params": []
},
{
"name": "Vote proposals",
"id": "VOTE_PROPOSAL_ROLE",
"params": []
}
],
"environments": {
"default": {
"network": "development",
"appName": "kredits-proposal.open.aragonpm.eth"
},
"rinkeby": {
"registry": "0x98df287b6c145399aaa709692c8d308357bc085d",
"appName": "kredits-proposal.open.aragonpm.eth",
"wsRPC": "wss://rinkeby.eth.aragon.network/ws",
"network": "rinkeby"
},
"production": {
"registry": "0x314159265dd8dbb310642f98f50c066173c1259b",
"appName": "proposal.open.aragonpm.eth",
"wsRPC": "wss://mainnet.eth.aragon.network/ws",
"network": "mainnet"
}
},
"path": "contracts/Proposal.sol"
}

View File

@ -1,130 +0,0 @@
pragma solidity ^0.4.24;
import "@aragon/os/contracts/apps/AragonApp.sol";
import "@aragon/os/contracts/kernel/IKernel.sol";
interface IContributor {
function getContributorAddressById(uint32 contributorId) public view returns (address);
function getContributorIdByAddress(address contributorAccount) public view returns (uint32);
function exists(uint32 contributorId) public view returns (bool);
}
interface IContribution {
function add(uint32 amount, uint32 contributorId, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public;
}
contract Proposal is AragonApp {
bytes32 public constant ADD_PROPOSAL_ROLE = keccak256("ADD_PROPOSAL_ROLE");
bytes32 public constant VOTE_PROPOSAL_ROLE = keccak256("VOTE_PROPOSAL_ROLE");
bytes32 public constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb;
// ensure alphabetic order
enum Apps { Contribution, Contributor, Proposal, Reimbursement, Token }
bytes32[5] public appIds;
struct Proposal {
address creatorAccount;
uint32 contributorId;
uint16 votesCount;
uint16 votesNeeded;
uint32 amount;
bool executed;
bytes32 hashDigest;
uint8 hashFunction;
uint8 hashSize;
uint32[] voterIds;
mapping (uint32 => bool) votes;
bool exists;
}
mapping(uint32 => Proposal) public proposals;
uint32 public proposalsCount;
event ProposalCreated(uint32 id, address creatorAccount, uint32 contributorId, uint32 amount);
event ProposalVoted(uint32 id, uint32 voterId, uint16 totalVotes);
event ProposalExecuted(uint32 id, uint32 contributorId, uint32 amount);
function initialize(bytes32[5] _appIds) public onlyInit {
appIds = _appIds;
initialized();
}
function getContract(uint8 appId) public view returns (address) {
IKernel k = IKernel(kernel());
return k.getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[appId]);
}
function addProposal(uint32 contributorId, uint32 amount, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public isInitialized auth(ADD_PROPOSAL_ROLE) {
require(IContributor(getContract(uint8(Apps.Contributor))).exists(contributorId), 'CONTRIBUTOR_NOT_FOUND');
uint32 proposalId = proposalsCount + 1;
uint16 _votesNeeded = 1; //contributorsContract().coreContributorsCount() / 100 * 75;
Proposal storage p = proposals[proposalId];
p.creatorAccount = msg.sender;
p.contributorId = contributorId;
p.amount = amount;
p.hashDigest = hashDigest;
p.hashFunction = hashFunction;
p.hashSize = hashSize;
p.votesCount = 0;
p.votesNeeded = _votesNeeded;
p.exists = true;
proposalsCount++;
emit ProposalCreated(proposalId, msg.sender, p.contributorId, p.amount);
}
function getProposal(uint32 proposalId) public view returns (uint32 id, address creatorAccount, uint32 contributorId, uint16 votesCount, uint16 votesNeeded, uint32 amount, bool executed, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, uint32[] voterIds, bool exists) {
id = proposalId;
Proposal storage p = proposals[id];
return (
id,
p.creatorAccount,
p.contributorId,
p.votesCount,
p.votesNeeded,
p.amount,
p.executed,
p.hashDigest,
p.hashFunction,
p.hashSize,
p.voterIds,
p.exists
);
}
function vote(uint32 proposalId) public isInitialized auth(VOTE_PROPOSAL_ROLE) {
Proposal storage p = proposals[proposalId];
require(!p.executed, 'ALREADY_EXECUTED');
uint32 voterId = IContributor(getContract(uint8(Apps.Contributor))).getContributorIdByAddress(msg.sender);
require(p.votes[voterId] != true, 'ALREADY_VOTED');
p.voterIds.push(voterId);
p.votes[voterId] = true;
p.votesCount++;
if (p.votesCount >= p.votesNeeded) {
executeProposal(proposalId);
}
emit ProposalVoted(proposalId, voterId, p.votesCount);
}
function batchVote(uint32[] _proposalIds) public isInitialized auth(VOTE_PROPOSAL_ROLE) {
for (uint32 i = 0; i < _proposalIds.length; i++) {
vote(_proposalIds[i]);
}
}
function executeProposal(uint32 proposalId) private {
Proposal storage p = proposals[proposalId];
require(!p.executed, 'ALREADY_EXECUTED');
require(p.votesCount >= p.votesNeeded, 'MISSING_VOTES');
p.executed = true;
IContribution(getContract(uint8(Apps.Contribution))).add(p.amount, p.contributorId, p.hashDigest, p.hashFunction, p.hashSize);
emit ProposalExecuted(proposalId, p.contributorId, p.amount);
}
}

View File

@ -1,23 +0,0 @@
pragma solidity ^0.4.24;
contract Migrations {
address public owner;
uint public last_completed_migration;
modifier restricted() {
if (msg.sender == owner) _;
}
constructor() public {
owner = msg.sender;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

View File

@ -1,4 +0,0 @@
{
"name": "Proposal",
"description": "Kredits Proposal app"
}

View File

@ -1,5 +0,0 @@
var Migrations = artifacts.require('./Migrations.sol')
module.exports = function (deployer) {
deployer.deploy(Migrations)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +0,0 @@
{
"name": "kredits-proposal",
"version": "1.0.0",
"description": "",
"dependencies": {
"@aragon/os": "^4.4.0"
},
"devDependencies": {
"@aragon/test-helpers": "^2.1.0",
"eth-gas-reporter": "^0.2.17",
"ganache-cli": "^6.9.1",
"solidity-coverage": "^0.5.11"
},
"scripts": {
"start": "npm run start:aragon:ipfs",
"start:aragon:ipfs": "aragon run",
"start:aragon:http": "aragon run --http localhost:8001 --http-served-from ./dist",
"start:app": "",
"compile": "aragon contracts compile",
"sync-assets": "",
"build:app": "",
"build:script": "",
"build": "",
"publish:patch": "aragon apm publish patch",
"publish:minor": "aragon apm publish minor",
"publish:major": "aragon apm publish major",
"versions": "aragon apm versions",
"test": "truffle test"
},
"keywords": []
}

View File

@ -1,5 +0,0 @@
// const Proposal = artifacts.require('Proposal.sol');
contract('Proposal', (_accounts) => {
it('should be tested');
});

View File

@ -1 +0,0 @@
module.exports = require("../../truffle.js");

View File

@ -1 +0,0 @@
*.sol linguist-language=Solidity

View File

@ -1,7 +0,0 @@
node_modules
artifacts
.cache
cache
dist
ipfs.cmd
package-lock.json

View File

@ -1,34 +0,0 @@
{
"roles": [
{
"name": "Add reimursements",
"id": "ADD_REIMBURSEMENT_ROLE",
"params": []
},
{
"name": "Veto reimbursement",
"id": "VETO_REIMBURSEMENT_ROLE",
"params": []
}
],
"environments": {
"default": {
"network": "development",
"appName": "kredits-reimbursement.open.aragonpm.eth"
},
"rinkeby": {
"registry": "0x98df287b6c145399aaa709692c8d308357bc085d",
"appName": "kredits-reimbursement.open.aragonpm.eth",
"wsRPC": "wss://rinkeby.eth.aragon.network/ws",
"network": "rinkeby"
},
"production": {
"registry": "0x314159265dd8dbb310642f98f50c066173c1259b",
"appName": "kredits-reimbursement.aragonpm.eth",
"wsRPC": "wss://mainnet.eth.aragon.network/ws",
"network": "mainnet"
}
},
"appName": "kredits-reimbursement.aragonpm.eth",
"path": "contracts/Reimbursement.sol"
}

View File

@ -1,34 +0,0 @@
const { usePlugin } = require('@nomiclabs/buidler/config')
const hooks = require('./scripts/buidler-hooks')
usePlugin('@aragon/buidler-aragon')
module.exports = {
// Default Buidler configurations. Read more about it at https://buidler.dev/config/
defaultNetwork: 'localhost',
networks: {
localhost: {
url: 'http://localhost:8545',
},
},
solc: {
version: '0.4.24',
optimizer: {
enabled: true,
runs: 10000,
},
},
// Etherscan plugin configuration. Learn more at https://github.com/nomiclabs/buidler/tree/master/packages/buidler-etherscan
etherscan: {
apiKey: '', // API Key for smart contract verification. Get yours at https://etherscan.io/apis
},
// Aragon plugin configuration
aragon: {
appServePort: 8001,
clientServePort: 3000,
appSrcPath: 'app/',
appBuildOutputPath: 'dist/',
appName: 'expenses',
hooks, // Path to script hooks
},
}

View File

@ -1,16 +0,0 @@
{
"name": "kredits Reimbursement",
"author": "Placeholder-author",
"description": "An application for Aragon",
"details_url": "/meta/details.md",
"source_url": "https://<placeholder-repository-url>",
"icons": [
{
"src": "/meta/icon.svg",
"sizes": "56x56"
}
],
"screenshots": [{ "src": "/meta/screenshot-1.png" }],
"start_url": "/index.html",
"script": "/script.js"
}

View File

@ -1,30 +0,0 @@
{
"name": "kredits-reimbursement",
"version": "1.0.0",
"description": "",
"scripts": {
"start": "npm run start:aragon:ipfs",
"start:aragon:ipfs": "aragon run",
"start:aragon:http": "aragon run --http localhost:8001 --http-served-from ./dist",
"start:app": "",
"compile": "aragon contracts compile",
"sync-assets": "",
"build:app": "",
"build:script": "",
"build": "",
"publish:patch": "aragon apm publish patch",
"publish:minor": "aragon apm publish minor",
"publish:major": "aragon apm publish major",
"versions": "aragon apm versions",
"test": "truffle test"
},
"dependencies": {
"@aragon/os": "^4.4.0"
},
"devDependencies": {
"@aragon/test-helpers": "^2.1.0",
"eth-gas-reporter": "^0.2.17",
"ganache-cli": "^6.9.1",
"solidity-coverage": "^0.5.11"
}
}

View File

@ -1,65 +0,0 @@
/*
const { hash } = require('eth-ens-namehash')
const { getEventArgument } = require('@aragon/contract-test-helpers/events')
const Kernel = artifacts.require('@aragon/os/build/contracts/kernel/Kernel')
const ACL = artifacts.require('@aragon/os/build/contracts/acl/ACL')
const EVMScriptRegistryFactory = artifacts.require(
'@aragon/os/build/contracts/factory/EVMScriptRegistryFactory'
)
const DAOFactory = artifacts.require(
'@aragon/os/build/contracts/factory/DAOFactory'
)
const newDao = async (rootAccount) => {
// Deploy a DAOFactory.
const kernelBase = await Kernel.new(true)
const aclBase = await ACL.new()
const registryFactory = await EVMScriptRegistryFactory.new()
const daoFactory = await DAOFactory.new(
kernelBase.address,
aclBase.address,
registryFactory.address
)
// Create a DAO instance.
const daoReceipt = await daoFactory.newDAO(rootAccount)
const dao = await Kernel.at(getEventArgument(daoReceipt, 'DeployDAO', 'dao'))
// Grant the rootAccount address permission to install apps in the DAO.
const acl = await ACL.at(await dao.acl())
const APP_MANAGER_ROLE = await kernelBase.APP_MANAGER_ROLE()
await acl.createPermission(
rootAccount,
dao.address,
APP_MANAGER_ROLE,
rootAccount,
{ from: rootAccount }
)
return { dao, acl }
}
const newApp = async (dao, appName, baseAppAddress, rootAccount) => {
const receipt = await dao.newAppInstance(
hash(`${appName}.aragonpm.test`), // appId - Unique identifier for each app installed in the DAO; can be any bytes32 string in the tests.
baseAppAddress, // appBase - Location of the app's base implementation.
'0x', // initializePayload - Used to instantiate and initialize the proxy in the same call (if given a non-empty bytes string).
false, // setDefault - Whether the app proxy is the default proxy.
{ from: rootAccount }
)
// Find the deployed proxy address in the tx logs.
const logs = receipt.logs
const log = logs.find((l) => l.event === 'NewAppProxy')
const proxyAddress = log.args.proxy
return proxyAddress
}
module.exports = {
newDao,
newApp,
}
*/

View File

@ -1,21 +0,0 @@
/*
const ANY_ADDRESS = '0xffffffffffffffffffffffffffffffffffffffff'
const setOpenPermission = async (acl, appAddress, role, rootAddress) => {
// Note: Setting a permission to 0xffffffffffffffffffffffffffffffffffffffff
// is interpreted by aragonOS as allowing the role for any address.
await acl.createPermission(
ANY_ADDRESS, // entity (who?) - The entity or address that will have the permission.
appAddress, // app (where?) - The app that holds the role involved in this permission.
role, // role (what?) - The particular role that the entity is being assigned to in this permission.
rootAddress, // manager - Can grant/revoke further permissions for this role.
{ from: rootAddress}
)
}
module.exports = {
setOpenPermission
}
*/

View File

@ -1 +0,0 @@
module.exports = require("../../truffle.js");

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +0,0 @@
node_modules
build
.cache
dist

View File

@ -1,14 +0,0 @@
# Git files
.gitignore
# Build files
.cache
node_modules
build
# Lock files
package-lock.json
yarn.lock
# Others
test

View File

@ -1 +0,0 @@
# Kredits Token

View File

@ -1,28 +0,0 @@
{
"roles": [
{
"name": "Mint token",
"id": "MINT_TOKEN_ROLE",
"params": []
}
],
"environments": {
"default": {
"network": "development",
"appName": "kredits-token.open.aragonpm.eth"
},
"rinkeby": {
"registry": "0x98df287b6c145399aaa709692c8d308357bc085d",
"appName": "kredits-token.open.aragonpm.eth",
"wsRPC": "wss://rinkeby.eth.aragon.network/ws",
"network": "rinkeby"
},
"mainnet": {
"registry": "0x314159265dd8dbb310642f98f50c066173c1259b",
"appName": "kredits-token.open.aragonpm.eth",
"wsRPC": "wss://mainnet.eth.aragon.network/ws",
"network": "mainnet"
}
},
"path": "contracts/Token.sol"
}

View File

@ -1,173 +0,0 @@
pragma solidity ^0.4.24;
import "@aragon/os/contracts/lib/math/SafeMath.sol";
/**
* beause ERC20.sol conflicts with the aragon ERC20.sol this is copied and modified from:
* https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/ERC20.sol
* @title Standard ERC20 token
*
* @dev Implementation of the basic standard token.
* https://eips.ethereum.org/EIPS/eip-20
* Originally based on code by FirstBlood:
* https://github.com/Firstbloodio/token/blob/master/smart_contract/FirstBloodToken.sol
*
* This implementation emits additional Approval events, allowing applications to reconstruct the allowance status for
* all accounts just by listening to said events. Note that this isn't required by the specification, and other
* compliant implementations may not do it.
*/
contract ERC20Token {
using SafeMath for uint256;
mapping (address => uint256) public _balances;
mapping (address => mapping (address => uint256)) private _allowed;
uint256 public _totalSupply;
string public name;
string public symbol;
uint8 public decimals;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Total number of tokens in existence
*/
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
/**
* @dev Gets the balance of the specified address.
* @param owner The address to query the balance of.
* @return A uint256 representing the amount owned by the passed address.
*/
function balanceOf(address owner) public view returns (uint256) {
return _balances[owner];
}
/**
* @dev Function to check the amount of tokens that an owner allowed to a spender.
* @param owner address The address which owns the funds.
* @param spender address The address which will spend the funds.
* @return A uint256 specifying the amount of tokens still available for the spender.
*/
function allowance(address owner, address spender) public view returns (uint256) {
return _allowed[owner][spender];
}
/**
* @dev Transfer token to a specified address
* @param to The address to transfer to.
* @param value The amount to be transferred.
*/
function transfer(address to, uint256 value) public returns (bool) {
_transfer(msg.sender, to, value);
return true;
}
/**
* @dev Approve the passed address to spend the specified amount of tokens on behalf of msg.sender.
* Beware that changing an allowance with this method brings the risk that someone may use both the old
* and the new allowance by unfortunate transaction ordering. One possible solution to mitigate this
* race condition is to first reduce the spender's allowance to 0 and set the desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
* @param spender The address which will spend the funds.
* @param value The amount of tokens to be spent.
*/
function approve(address spender, uint256 value) public returns (bool) {
_approve(msg.sender, spender, value);
return true;
}
/**
* @dev Transfer tokens from one address to another.
* Note that while this function emits an Approval event, this is not required as per the specification,
* and other compliant implementations may not emit the event.
* @param from address The address which you want to send tokens from
* @param to address The address which you want to transfer to
* @param value uint256 the amount of tokens to be transferred
*/
function transferFrom(address from, address to, uint256 value) public returns (bool) {
_transfer(from, to, value);
_approve(from, msg.sender, _allowed[from][msg.sender].sub(value));
return true;
}
/**
* @dev Increase the amount of tokens that an owner allowed to a spender.
* approve should be called when _allowed[msg.sender][spender] == 0. To increment
* allowed value is better to use this function to avoid 2 calls (and wait until
* the first transaction is mined)
* From MonolithDAO Token.sol
* Emits an Approval event.
* @param spender The address which will spend the funds.
* @param addedValue The amount of tokens to increase the allowance by.
*/
function increaseAllowance(address spender, uint256 addedValue) public returns (bool) {
_approve(msg.sender, spender, _allowed[msg.sender][spender].add(addedValue));
return true;
}
/**
* @dev Decrease the amount of tokens that an owner allowed to a spender.
* approve should be called when _allowed[msg.sender][spender] == 0. To decrement
* allowed value is better to use this function to avoid 2 calls (and wait until
* the first transaction is mined)
* From MonolithDAO Token.sol
* Emits an Approval event.
* @param spender The address which will spend the funds.
* @param subtractedValue The amount of tokens to decrease the allowance by.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public returns (bool) {
_approve(msg.sender, spender, _allowed[msg.sender][spender].sub(subtractedValue));
return true;
}
/**
* @dev Transfer token for a specified addresses
* @param from The address to transfer from.
* @param to The address to transfer to.
* @param value The amount to be transferred.
*/
function _transfer(address from, address to, uint256 value) internal {
require(to != address(0));
_balances[from] = _balances[from].sub(value);
_balances[to] = _balances[to].add(value);
emit Transfer(from, to, value);
}
/**
* @dev Internal function that mints an amount of the token and assigns it to
* an account. This encapsulates the modification of balances such that the
* proper events are emitted.
* @param account The account that will receive the created tokens.
* @param value The amount that will be created.
*/
function _mint(address account, uint256 value) internal {
require(account != address(0), 'invalid address');
_totalSupply = _totalSupply.add(value);
_balances[account] = _balances[account].add(value);
emit Transfer(address(0), account, value);
}
/**
* @dev Approve an address to spend another addresses' tokens.
* @param owner The address that owns the tokens.
* @param spender The address that will spend the tokens.
* @param value The number of tokens that can be spent.
*/
function _approve(address owner, address spender, uint256 value) internal {
require(spender != address(0));
require(owner != address(0));
_allowed[owner][spender] = value;
emit Approval(owner, spender, value);
}
}

View File

@ -1,31 +0,0 @@
pragma solidity ^0.4.24;
import "@aragon/os/contracts/apps/AragonApp.sol";
import "./ERC20Token.sol";
contract Token is ERC20Token, AragonApp {
bytes32 public constant MINT_TOKEN_ROLE = keccak256("MINT_TOKEN_ROLE");
// ensure alphabetic order
enum Apps { Contribution, Contributor, Proposal, Reimbursement, Token }
bytes32[5] public appIds;
event LogMint(address indexed recipient, uint256 amount, uint32 contributionId);
function initialize(bytes32[5] _appIds) public onlyInit {
appIds = _appIds;
name = 'Kredits';
symbol = '₭S';
decimals = 18;
initialized();
}
function mintFor(address contributorAccount, uint256 amount, uint32 contributionId) public isInitialized auth(MINT_TOKEN_ROLE) {
require(amount > 0, "INVALID_AMOUNT");
uint256 amountInWei = amount.mul(1 ether);
_mint(contributorAccount, amountInWei);
emit LogMint(contributorAccount, amount, contributionId);
}
}

View File

@ -1,23 +0,0 @@
pragma solidity ^0.4.24;
contract Migrations {
address public owner;
uint public last_completed_migration;
modifier restricted() {
if (msg.sender == owner) _;
}
constructor() public {
owner = msg.sender;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

View File

@ -1,16 +0,0 @@
pragma solidity ^0.4.24;
import "@aragon/os/contracts/acl/ACL.sol";
import "@aragon/os/contracts/kernel/Kernel.sol";
import "@aragon/os/contracts/factory/DAOFactory.sol";
// You might think this file is a bit odd, but let me explain.
// We only use for now those imported contracts in our tests, which
// means Truffle will not compile them for us, because they are from
// an external dependency.
// solium-disable-next-line no-empty-blocks
contract Spoof {
// ...
}

View File

@ -1,4 +0,0 @@
{
"name": "Token",
"description": "Kredits Token app"
}

View File

@ -1,5 +0,0 @@
var Migrations = artifacts.require('./Migrations.sol')
module.exports = function (deployer) {
deployer.deploy(Migrations)
}

View File

@ -1,5 +0,0 @@
var Token = artifacts.require('Token.sol')
module.exports = function (deployer) {
deployer.deploy(Token)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,31 +0,0 @@
{
"name": "kredits-token",
"version": "1.0.0",
"description": "",
"dependencies": {
"@aragon/os": "^4.4.0"
},
"devDependencies": {
"@aragon/test-helpers": "^2.1.0",
"eth-gas-reporter": "^0.2.17",
"ganache-cli": "^6.9.1",
"solidity-coverage": "^0.5.11"
},
"scripts": {
"start": "npm run start:aragon:ipfs",
"start:aragon:ipfs": "aragon run",
"start:aragon:http": "aragon run --http localhost:8001 --http-served-from ./dist",
"start:app": "",
"compile": "aragon contracts compile",
"sync-assets": "",
"build:app": "",
"build:script": "",
"build": "",
"publish:patch": "aragon apm publish patch",
"publish:minor": "aragon apm publish minor",
"publish:major": "aragon apm publish major",
"versions": "aragon apm versions",
"test": "truffle test"
},
"keywords": []
}

View File

@ -1,123 +0,0 @@
const namehash = require('ethers').utils.namehash;
// eslint-disable-next-line no-undef
const Token = artifacts.require("Token.sol");
// eslint-disable-next-line no-undef
const getContract = name => artifacts.require(name);
const { assertRevert } = require('@aragon/test-helpers/assertThrow');
const ZERO_ADDR = '0x0000000000000000000000000000000000000000';
contract('Token app', (accounts) => {
let kernelBase, aclBase, daoFactory, dao, r, acl, token;
const root = accounts[0];
const member1 = accounts[1];
// eslint-disable-next-line no-undef
before(async () => {
kernelBase = await getContract('Kernel').new(true); // petrify immediately
aclBase = await getContract('ACL').new();
daoFactory = await getContract('DAOFactory').new(kernelBase.address, aclBase.address, ZERO_ADDR);
r = await daoFactory.newDAO(root);
dao = await getContract('Kernel').at(r.logs.filter(l => l.event == 'DeployDAO')[0].args.dao);
acl = await getContract('ACL').at(await dao.acl());
//create dao mamnager permission for coin owner
await acl.createPermission(
root,
dao.address,
await dao.APP_MANAGER_ROLE(),
root,
{ from: root }
);
//get new app instance from DAO
const receipt = await dao.newAppInstance(
'0x1234',
(await Token.new()).address,
0x0,
false,
{ from: root }
);
token = Token.at(
receipt.logs.filter(l => l.event == 'NewAppProxy')[0].args.proxy
);
//apps id
let appsId = [];
appsId[0] = namehash("kredits-contribution");
appsId[1] = namehash("kredits-contributor");
appsId[2] = namehash("kredits-proposal");
appsId[3] = namehash("kredits-token");
//init token (app)
await token.initialize(appsId);
//create token mint permission for coin owner
await acl.createPermission(
root,
token.address,
await token.MINT_TOKEN_ROLE(),
root,
{ from: root }
);
});
describe("Owner default space permissions", async () => {
it('check owner is token issuer', async () => {
let tokenIssuerPermission = await acl.hasPermission(root, token.address, await token.MINT_TOKEN_ROLE());
// eslint-disable-next-line no-undef
assert.equal(tokenIssuerPermission, true);
});
});
describe("Token issuing", async () => {
let name = "Kredits";
let symbol = "₭S";
let decimals = 18;
it("check token properties", async () => {
assert.equal(await token.name(), name); // eslint-disable-line no-undef
assert.equal(await token.symbol(), symbol); // eslint-disable-line no-undef
assert.equal(await token.decimals(), decimals); // eslint-disable-line no-undef
});
});
describe("Token minting", async () => {
let tokenToMint = 250;
let ether = 1000000000000000000;
it("should revert when mint tokens from an address that does not have minting permission", async () => {
return assertRevert(async () => {
await token.mintFor(root, tokenToMint, 1, { from: member1});
'address does not have permission to mint tokens';
});
});
it("should revert when mint tokens to address(0)", async () => {
return assertRevert(async () => {
await token.mintFor(ZERO_ADDR, tokenToMint, 1, { from: root});
'invalid contributor address';
});
});
it("should revert when mint amount of tokens equal to 0", async () => {
return assertRevert(async () => {
await token.mintFor(root, 0, 1, { from: root});
'amount to mint should be greater than zero';
});
});
it("mint tokens", async () => {
await token.mintFor(root, tokenToMint, 1, { from: root });
let ownerBalance = await token.balanceOf(root);
let totalSupply = await token.totalSupply();
assert.equal(ownerBalance.toNumber(), tokenToMint*ether); // eslint-disable-line no-undef
assert.equal(totalSupply.toNumber(), tokenToMint*ether); // eslint-disable-line no-undef
});
});
});

View File

@ -1 +0,0 @@
module.exports = require("../../truffle.js");

View File

@ -1,59 +0,0 @@
{
"roles": [
{
"name": "Add contributions",
"id": "ADD_CONTRIBUTION_ROLE",
"params": []
},
{
"name": "Veto contributions",
"id": "VETO_CONTRIBUTION_ROLE",
"params": []
},
{
"name": "Manage contributors",
"id": "MANAGE_CONTRIBUTORS_ROLE",
"params": []
},
{
"name": "Mint token",
"id": "MINT_TOKEN_ROLE",
"params": []
},
{
"name": "Add proposal",
"id": "ADD_PROPOSAL_ROLE",
"params": []
},
{
"name": "Vote proposal",
"id": "VOTE_PROPOSAL_ROLE",
"params": []
}
],
"environments": {
"development": {
"network": "development",
"registry": "0x5f6f7e8cc7346a11ca2def8f827b7a0b612c56a1",
"appName": "dummy.open.aragonpm.eth"
},
"rinkeby": {
"network": "rinkeby",
"registry": "0x98Df287B6C145399Aaa709692c8D308357bC085D",
"wsRPC": "wss://rinkeby.eth.aragon.network/ws",
"appName": "dummy.open.aragonpm.eth",
"kredits": {
"daoFactory": "0x2298d27a9b847c681d2b2c2828ab9d79013f5f1d"
}
},
"kovan": {
"network": "kovan",
"appName": "dummy.aragonpm.eth"
},
"default": {
"network": "development",
"appName": "dummy.aragonpm.eth"
}
},
"path": "contracts/misc/DummyApp.sol"
}

View File

@ -1,28 +1,22 @@
pragma solidity ^0.4.24;
pragma solidity ^0.8.0;
import "@aragon/os/contracts/apps/AragonApp.sol";
import "@aragon/os/contracts/kernel/IKernel.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
interface IToken {
function mintFor(address contributorAccount, uint256 amount, uint32 contributionId) public;
function mintFor(address contributorAccount, uint256 amount, uint32 contributionId) external;
}
interface ContributorInterface {
function getContributorAddressById(uint32 contributorId) public view returns (address);
function getContributorIdByAddress(address contributorAccount) public view returns (uint32);
function getContributorAddressById(uint32 contributorId) external view returns (address);
function getContributorIdByAddress(address contributorAccount) external view returns (uint32);
function addressIsCore(address sender) external view returns (bool);
// TODO Maybe use for validation
// function exists(uint32 contributorId) public view returns (bool);
}
contract Contribution is AragonApp {
bytes32 public constant ADD_CONTRIBUTION_ROLE = keccak256("ADD_CONTRIBUTION_ROLE");
bytes32 public constant VETO_CONTRIBUTION_ROLE = keccak256("VETO_CONTRIBUTION_ROLE");
bytes32 public constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb;
// ensure alphabetic order
enum Apps { Contribution, Contributor, Proposal, Reimbursement, Token }
bytes32[5] public appIds;
contract Contribution is Initializable {
ContributorInterface public contributorContract;
IToken public tokenContract;
struct ContributionData {
uint32 contributorId;
@ -54,36 +48,42 @@ contract Contribution is AragonApp {
event ContributionClaimed(uint32 id, uint32 indexed contributorId, uint32 amount);
event ContributionVetoed(uint32 id, address vetoedByAccount);
function initialize(bytes32[5] _appIds) public onlyInit {
appIds = _appIds;
blocksToWait = 40320; // 7 days; 15 seconds block time
initialized();
modifier onlyCore {
require(contributorContract.addressIsCore(tx.origin), "Core only");
_;
}
function getContract(uint8 appId) public view returns (address) {
IKernel k = IKernel(kernel());
return k.getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[appId]);
function initialize(uint32 blocksToWait_) public initializer {
blocksToWait = blocksToWait_;
}
function setTokenContract(address token) public {
require(address(tokenContract) == address(0) || contributorContract.addressIsCore(msg.sender), "Core only");
tokenContract = IToken(token);
}
function setContributorContract(address contributor) public {
require(address(contributorContract) == address(0) || contributorContract.addressIsCore(msg.sender), "Core only");
contributorContract = ContributorInterface(contributor);
}
function getContributorIdByAddress(address contributorAccount) public view returns (uint32) {
address contributorContract = getContract(uint8(Apps.Contributor));
return ContributorInterface(contributorContract).getContributorIdByAddress(contributorAccount);
return contributorContract.getContributorIdByAddress(contributorAccount);
}
function getContributorAddressById(uint32 contributorId) public view returns (address) {
address contributorContract = getContract(uint8(Apps.Contributor));
return ContributorInterface(contributorContract).getContributorAddressById(contributorId);
return contributorContract.getContributorAddressById(contributorId);
}
//
// Token standard functions (ERC 721)
//
function name() external view returns (string) {
function name() external view returns (string memory) {
return name_;
}
function symbol() external view returns (string) {
function symbol() external view returns (string memory) {
return symbol_;
}
@ -105,7 +105,7 @@ contract Contribution is AragonApp {
return ownedContributions[contributorId][index];
}
function tokenMetadata(uint32 contributionId) public view returns (string) {
function tokenMetadata(uint32 contributionId) public view returns (string memory) {
return contributions[contributionId].tokenMetadataURL;
}
@ -150,8 +150,9 @@ contract Contribution is AragonApp {
);
}
function add(uint32 amount, uint32 contributorId, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public isInitialized auth(ADD_CONTRIBUTION_ROLE) {
function add(uint32 amount, uint32 contributorId, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public {
//require(canPerform(msg.sender, ADD_CONTRIBUTION_ROLE, new uint32[](0)), 'nope');
require(balanceOf(msg.sender) > 0 || contributorContract.addressIsCore(msg.sender), 'must have kredits or core');
uint32 contributionId = contributionsCount + 1;
ContributionData storage c = contributions[contributionId];
c.exists = true;
@ -175,7 +176,8 @@ contract Contribution is AragonApp {
emit ContributionAdded(contributionId, contributorId, amount);
}
function veto(uint32 contributionId) public isInitialized auth(VETO_CONTRIBUTION_ROLE) {
function veto(uint32 contributionId) public onlyCore {
ContributionData storage c = contributions[contributionId];
require(c.exists, 'NOT_FOUND');
require(!c.claimed, 'ALREADY_CLAIMED');
@ -185,7 +187,7 @@ contract Contribution is AragonApp {
emit ContributionVetoed(contributionId, msg.sender);
}
function claim(uint32 contributionId) public isInitialized {
function claim(uint32 contributionId) public {
ContributionData storage c = contributions[contributionId];
require(c.exists, 'NOT_FOUND');
require(!c.claimed, 'ALREADY_CLAIMED');
@ -193,14 +195,14 @@ contract Contribution is AragonApp {
require(block.number >= c.confirmedAtBlock, 'NOT_CLAIMABLE');
c.claimed = true;
address tokenContract = getContract(uint8(Apps.Token));
address contributorAccount = getContributorAddressById(c.contributorId);
uint256 amount = uint256(c.amount);
IToken(tokenContract).mintFor(contributorAccount, amount, contributionId);
tokenContract.mintFor(contributorAccount, amount, contributionId);
emit ContributionClaimed(contributionId, c.contributorId, c.amount);
}
function exists(uint32 contributionId) public view returns (bool) {
return contributions[contributionId].exists;
}
}

View File

@ -1,19 +1,19 @@
pragma solidity ^0.4.24;
pragma solidity ^0.8.0;
import "@aragon/os/contracts/apps/AragonApp.sol";
import "@aragon/os/contracts/kernel/IKernel.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
interface ITokenBalance {
function balanceOf(address contributorAccount) public view returns (uint256);
function balanceOf(address contributorAccount) external view returns (uint256);
}
interface IContributionBalance {
function totalKreditsEarnedByContributor(uint32 contributorId, bool confirmedOnly) public view returns (uint32 amount);
function balanceOf(address owner) public view returns (uint256);
function totalKreditsEarnedByContributor(uint32 contributorId, bool confirmedOnly) external view returns (uint32 amount);
function balanceOf(address owner) external view returns (uint256);
}
contract Contributor is AragonApp {
bytes32 public constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb;
bytes32 public constant MANAGE_CONTRIBUTORS_ROLE = keccak256("MANAGE_CONTRIBUTORS_ROLE");
contract Contributor is Initializable {
address deployer;
IContributionBalance public contributionContract;
ITokenBalance public tokenContract;
struct Contributor {
address account;
@ -27,23 +27,27 @@ contract Contributor is AragonApp {
mapping (uint32 => Contributor) public contributors;
uint32 public contributorsCount;
// ensure alphabetic order
enum Apps { Contribution, Contributor, Proposal, Reimbursement, Token }
bytes32[5] public appIds;
event ContributorProfileUpdated(uint32 id, bytes32 oldHashDigest, bytes32 newHashDigest); // what should be logged
event ContributorAccountUpdated(uint32 id, address oldAccount, address newAccount);
event ContributorAdded(uint32 id, address account);
function initialize(address root, bytes32[5] _appIds) public onlyInit {
appIds = _appIds;
initialized();
modifier onlyCore {
require(addressIsCore(tx.origin), "Core only");
_;
}
function getContract(uint8 appId) public view returns (address) {
IKernel k = IKernel(kernel());
return k.getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[appId]);
function initialize() public initializer {
deployer = msg.sender;
}
function setContributionContract(address contribution) public onlyCore {
require(address(contributionContract) == address(0) || addressIsCore(msg.sender), "Core only");
contributionContract = IContributionBalance(contribution);
}
function setTokenContract(address token) public onlyCore {
require(address(tokenContract) == address(0) || addressIsCore(msg.sender), "Core only");
tokenContract = ITokenBalance(token);
}
function coreContributorsCount() public view returns (uint32) {
@ -56,7 +60,7 @@ contract Contributor is AragonApp {
return count;
}
function updateContributorAccount(uint32 id, address oldAccount, address newAccount) public auth(MANAGE_CONTRIBUTORS_ROLE) {
function updateContributorAccount(uint32 id, address oldAccount, address newAccount) public onlyCore {
require(newAccount != address(0), "invalid new account address");
require(getContributorAddressById(id) == oldAccount, "contributor does not exist");
@ -66,7 +70,7 @@ contract Contributor is AragonApp {
emit ContributorAccountUpdated(id, oldAccount, newAccount);
}
function updateContributorProfileHash(uint32 id, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public isInitialized auth(MANAGE_CONTRIBUTORS_ROLE) {
function updateContributorProfileHash(uint32 id, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public onlyCore {
Contributor storage c = contributors[id];
bytes32 oldHashDigest = c.hashDigest;
c.hashDigest = hashDigest;
@ -76,7 +80,7 @@ contract Contributor is AragonApp {
ContributorProfileUpdated(id, oldHashDigest, c.hashDigest);
}
function addContributor(address account, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public isInitialized auth(MANAGE_CONTRIBUTORS_ROLE) {
function addContributor(address account, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public onlyCore {
require(!addressExists(account));
uint32 _id = contributorsCount + 1;
assert(!contributors[_id].exists); // this can not be acually
@ -95,7 +99,7 @@ contract Contributor is AragonApp {
function isCoreTeam(uint32 id) view public returns (bool) {
// TODO: for simplicity we simply define the first contributors as core
// later this needs to be changed to something more dynamic
return id < 7;
return id > 0 && id < 7;
}
function exists(uint32 id) view public returns (bool) {
@ -103,6 +107,10 @@ contract Contributor is AragonApp {
}
function addressIsCore(address account) view public returns (bool) {
// the deployer is always core
if(account == deployer) {
return true;
}
uint32 id = getContributorIdByAddress(account);
return isCoreTeam(id);
}
@ -119,7 +127,7 @@ contract Contributor is AragonApp {
return contributors[id].account;
}
function getContributorByAddress(address account) internal view returns (Contributor) {
function getContributorByAddress(address account) internal view returns (Contributor memory) {
uint32 id = contributorIds[account];
return contributors[id];
}
@ -132,24 +140,10 @@ contract Contributor is AragonApp {
hashFunction = c.hashFunction;
hashSize = c.hashSize;
isCore = isCoreTeam(id);
address token = getContract(uint8(Apps.Token));
balance = ITokenBalance(token).balanceOf(c.account);
address contribution = getContract(uint8(Apps.Contribution));
totalKreditsEarned = IContributionBalance(contribution).totalKreditsEarnedByContributor(_id, true);
contributionsCount = IContributionBalance(contribution).balanceOf(c.account);
balance = tokenContract.balanceOf(c.account);
totalKreditsEarned = contributionContract.totalKreditsEarnedByContributor(_id, true);
contributionsCount = contributionContract.balanceOf(c.account);
exists = c.exists;
}
function canPerform(address _who, address _where, bytes32 _what, uint256[] memory _how) public returns (bool) {
address sender = _who;
if (sender == address(-1)) {
sender = tx.origin;
}
// _what == keccak256('VOTE_PROPOSAL_ROLE')
if (_what == 0xd61216798314d2fc33e42ff2021d66707b1e38517d3f7166798a9d3a196a9c96) {
return contributorIds[sender] != uint256(0);
}
return addressIsCore(sender);
}
}

View File

@ -1,99 +0,0 @@
pragma solidity 0.4.24;
import "@aragon/os/contracts/apps/AragonApp.sol";
import "@aragon/os/contracts/kernel/Kernel.sol";
import "@aragon/os/contracts/acl/ACL.sol";
import "@aragon/kits-base/contracts/KitBase.sol";
import "../apps/contribution/contracts/Contribution.sol";
import "../apps/contributor/contracts/Contributor.sol";
import "../apps/token/contracts/Token.sol";
import "../apps/proposal/contracts/Proposal.sol";
import "../apps/reimbursement/contracts/Reimbursement.sol";
contract KreditsKit is KitBase {
// ensure alphabetic order
enum Apps { Contribution, Contributor, Proposal, Reimbursement, Token }
bytes32[5] public appIds;
event DeployInstance(address dao);
event InstalledApp(address dao, address appProxy, bytes32 appId);
constructor (DAOFactory _fac, ENS _ens, bytes32[5] _appIds) public KitBase(_fac, _ens) {
appIds = _appIds;
}
function newInstance() public returns (Kernel dao) {
address root = msg.sender;
dao = fac.newDAO(this);
ACL acl = ACL(dao.acl());
acl.createPermission(this, dao, dao.APP_MANAGER_ROLE(), this);
Contributor contributor = Contributor(_installApp(dao, appIds[uint8(Apps.Contributor)]));
contributor.initialize(root, appIds);
acl.createPermission(root, contributor, contributor.MANAGE_CONTRIBUTORS_ROLE(), this);
Token token = Token(_installApp(dao, appIds[uint8(Apps.Token)]));
token.initialize(appIds);
Contribution contribution = Contribution(_installApp(dao, appIds[uint8(Apps.Contribution)]));
contribution.initialize(appIds);
acl.createPermission(root, contribution, contribution.ADD_CONTRIBUTION_ROLE(), this);
acl.createPermission(root, contribution, contribution.VETO_CONTRIBUTION_ROLE(), this);
acl.grantPermission(proposal, contribution, contribution.ADD_CONTRIBUTION_ROLE());
Proposal proposal = Proposal(_installApp(dao, appIds[uint8(Apps.Proposal)]));
proposal.initialize(appIds);
Reimbursement reimbursement = Reimbursement(_installApp(dao, appIds[uint8(Apps.Reimbursement)]));
reimbursement.initialize();
acl.createPermission(root, reimbursement, reimbursement.ADD_REIMBURSEMENT_ROLE(), this);
acl.createPermission(root, reimbursement, reimbursement.VETO_REIMBURSEMENT_ROLE(), this);
uint256[] memory params = new uint256[](1);
params[0] = uint256(203) << 248 | uint256(1) << 240 | uint240(contributor);
acl.grantPermissionP(acl.ANY_ENTITY(), contribution, contribution.ADD_CONTRIBUTION_ROLE(), params);
acl.grantPermissionP(acl.ANY_ENTITY(), contribution, contribution.VETO_CONTRIBUTION_ROLE(), params);
acl.grantPermissionP(acl.ANY_ENTITY(), contributor, contributor.MANAGE_CONTRIBUTORS_ROLE(), params);
acl.grantPermissionP(acl.ANY_ENTITY(), reimbursement, reimbursement.ADD_REIMBURSEMENT_ROLE(), params);
//acl.setPermissionManager(this, proposal, proposal.VOTE_PROPOSAL_ROLE();
acl.createPermission(root, proposal, proposal.VOTE_PROPOSAL_ROLE(), this);
acl.grantPermissionP(acl.ANY_ENTITY(), proposal, proposal.VOTE_PROPOSAL_ROLE(), params);
acl.createPermission(root, proposal, proposal.ADD_PROPOSAL_ROLE(), this);
//acl.grantPermissionP(address(-1), proposal, proposal.ADD_PROPOSAL_ROLE(), params);
acl.grantPermission(acl.ANY_ENTITY(), proposal, proposal.ADD_PROPOSAL_ROLE());
acl.setPermissionManager(root, proposal, proposal.VOTE_PROPOSAL_ROLE());
acl.setPermissionManager(root, proposal, proposal.ADD_PROPOSAL_ROLE());
acl.setPermissionManager(root, contribution, contribution.ADD_CONTRIBUTION_ROLE());
acl.setPermissionManager(root, contribution, contribution.VETO_CONTRIBUTION_ROLE());
acl.setPermissionManager(root, contributor, contributor.MANAGE_CONTRIBUTORS_ROLE());
acl.setPermissionManager(root, reimbursement, reimbursement.ADD_REIMBURSEMENT_ROLE());
acl.setPermissionManager(root, reimbursement, reimbursement.VETO_REIMBURSEMENT_ROLE());
acl.createPermission(root, token, token.MINT_TOKEN_ROLE(), this);
acl.grantPermission(contribution, token, token.MINT_TOKEN_ROLE());
acl.setPermissionManager(root, token, token.MINT_TOKEN_ROLE());
cleanupDAOPermissions(dao, acl, root);
emit DeployInstance(dao);
return dao;
}
function _installApp(Kernel _dao, bytes32 _appId) internal returns (AragonApp) {
address baseAppAddress = latestVersionAppBase(_appId);
require(baseAppAddress != address(0), "App should be deployed");
AragonApp appProxy = AragonApp(_dao.newAppInstance(_appId, baseAppAddress, new bytes(0), true));
emit InstalledApp(_dao, appProxy, _appId);
return appProxy;
}
}

View File

@ -1,12 +1,17 @@
pragma solidity ^0.4.24;
pragma solidity ^0.8.0;
import "@aragon/os/contracts/apps/AragonApp.sol";
import "@aragon/os/contracts/kernel/IKernel.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract Reimbursement is AragonApp {
bytes32 public constant ADD_REIMBURSEMENT_ROLE = keccak256("ADD_REIMBURSEMENT_ROLE");
bytes32 public constant VETO_REIMBURSEMENT_ROLE = keccak256("VETO_REIMBURSEMENT_ROLE");
// bytes32 public constant MANAGE_APPS_ROLE = keccak256("MANAGE_APPS_ROLE");
interface ContributorInterface {
function getContributorAddressById(uint32 contributorId) external view returns (address);
function getContributorIdByAddress(address contributorAccount) external view returns (uint32);
function addressIsCore(address sender) external view returns (bool);
// TODO Maybe use for validation
// function exists(uint32 contributorId) public view returns (bool);
}
contract Reimbursement is Initializable {
ContributorInterface public contributorContract;
struct ReimbursementData {
uint32 recipientId;
@ -28,13 +33,27 @@ contract Reimbursement is AragonApp {
event ReimbursementAdded(uint32 id, address indexed addedByAccount, uint256 amount);
event ReimbursementVetoed(uint32 id, address vetoedByAccount);
function initialize() public onlyInit {
function initialize() public initializer {
blocksToWait = 40320; // 7 days; 15 seconds block time
initialized();
}
// function setApps() public isInitialized auth(MANAGE_APPS_ROLE) {
// }
modifier onlyCore {
require(contributorContract.addressIsCore(tx.origin), "Core only");
_;
}
function setContributorContract(address contributor) public {
require(address(contributorContract) == address(0) || contributorContract.addressIsCore(msg.sender), "Core only");
contributorContract = ContributorInterface(contributor);
}
function getContributorIdByAddress(address contributorAccount) public view returns (uint32) {
return contributorContract.getContributorIdByAddress(contributorAccount);
}
function getContributorAddressById(uint32 contributorId) public view returns (address) {
return contributorContract.getContributorAddressById(contributorId);
}
function totalAmount(bool confirmedOnly) public view returns (uint256 amount) {
for (uint32 i = 1; i <= reimbursementsCount; i++) {
@ -62,7 +81,7 @@ contract Reimbursement is AragonApp {
);
}
function add(uint256 amount, address token, uint32 recipientId, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public isInitialized auth(ADD_REIMBURSEMENT_ROLE) {
function add(uint256 amount, address token, uint32 recipientId, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public onlyCore {
uint32 reimbursementId = reimbursementsCount + 1;
ReimbursementData storage r = reimbursements[reimbursementId];
r.exists = true;
@ -79,7 +98,7 @@ contract Reimbursement is AragonApp {
emit ReimbursementAdded(reimbursementId, msg.sender, amount);
}
function veto(uint32 reimbursementId) public isInitialized auth(VETO_REIMBURSEMENT_ROLE) {
function veto(uint32 reimbursementId) public onlyCore {
ReimbursementData storage r = reimbursements[reimbursementId];
require(r.exists, 'NOT_FOUND');
require(block.number < r.confirmedAtBlock, 'VETO_PERIOD_ENDED');

44
contracts/Token.sol Normal file
View File

@ -0,0 +1,44 @@
pragma solidity ^0.8.0;
import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/math/SafeMathUpgradeable.sol";
interface ContributorInterface {
function getContributorAddressById(uint32 contributorId) external view returns (address);
function getContributorIdByAddress(address contributorAccount) external view returns (uint32);
function addressIsCore(address sender) external view returns (bool);
// TODO Maybe use for validation
// function exists(uint32 contributorId) public view returns (bool);
}
contract Token is Initializable, ERC20Upgradeable {
ContributorInterface public contributorContract;
using SafeMathUpgradeable for uint256;
address public contributionContract;
event LogMint(address indexed recipient, uint256 amount, uint32 contributionId);
function initialize() public virtual initializer {
__ERC20_init('Kredits', 'KS');
}
function setContributionContract(address contribution) public {
require(address(contributionContract) == address(0) || contributorContract.addressIsCore(msg.sender), "Core only");
contributionContract = contribution;
}
function setContributorContract(address contributor) public {
require(address(contributorContract) == address(0) || contributorContract.addressIsCore(msg.sender), "Core only");
contributorContract = ContributorInterface(contributor);
}
function mintFor(address contributorAccount, uint256 amount, uint32 contributionId) public {
require(contributionContract == msg.sender, "Only Contribution");
require(amount > 0, "INVALID_AMOUNT");
uint256 amountInWei = amount.mul(1 ether);
_mint(contributorAccount, amountInWei);
emit LogMint(contributorAccount, amount, contributionId);
}
}

View File

@ -1,13 +0,0 @@
pragma solidity 0.4.24;
import "@aragon/os/contracts/apm/APMNamehash.sol";
contract APMNamehashOpen is APMNamehash {
bytes32 public constant OPEN_TITLE = keccak256("open");
bytes32 public constant OPEN_APM_NODE = keccak256(abi.encodePacked(APM_NODE, OPEN_TITLE));
function apmNamehashOpen(string name) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(OPEN_APM_NODE, keccak256(name)));
}
}

View File

@ -1,16 +0,0 @@
pragma solidity 0.4.24;
import "@aragon/os/contracts/apps/AragonApp.sol";
// This is a "Dummy" app which's only purpose to exist is because
// Aragon's CLI still doesn't support running a Kit inside a project
// which isn't considered to be a "valid" Aragon project.
// It requires us to have an arrap.json file pointing to the contract
// and a manifest.json file which describes the front-end structure.
contract DummyApp is AragonApp {
function initialize() public onlyInit {
initialized();
}
}

View File

@ -1,23 +0,0 @@
pragma solidity ^0.4.4;
contract Migrations {
address public owner;
uint public last_completed_migration;
modifier restricted() {
if (msg.sender == owner) _;
}
function constructor() public {
owner = msg.sender;
}
function setCompleted(uint completed) public restricted {
last_completed_migration = completed;
}
function upgrade(address new_address) public restricted {
Migrations upgraded = Migrations(new_address);
upgraded.setCompleted(last_completed_migration);
}
}

89
hardhat.config.js Normal file
View File

@ -0,0 +1,89 @@
require("@nomiclabs/hardhat-waffle");
require("hardhat-deploy");
require("hardhat-deploy-ethers");
require("@openzeppelin/hardhat-upgrades");
const Kredits = require("./lib/kredits");
const promptly = require("promptly");
extendEnvironment(async (hre) => {
hre.kredits = new Kredits(
hre.ethers.provider,
hre.ethers.provider.getSigner()
);
await hre.kredits.init();
});
// This is a sample Hardhat task. To learn how to create your own go to
// https://hardhat.org/guides/create-task.html
task("accounts", "Prints the list of accounts", async () => {
const accounts = await ethers.getSigners();
for (const account of accounts) {
console.log(account.address);
}
});
task("fund", "Send eth to an address", async () => {
const to = await promptly.prompt("Address:");
const value = await promptly.prompt("Value:");
const signer = await ethers.getSigners();
const fundTransaction = await signer[0].sendTransaction({
to: to,
value: ethers.utils.parseEther(value),
});
console.log(fundTransaction);
});
task("create-wallet", "Creates a new wallet json", async () => {
const wallet = ethers.Wallet.createRandom();
console.log("New wallet:");
console.log(`Address: ${wallet.address}`);
console.log(`Public key: ${wallet.publicKey}`);
console.log(`Private key: ${wallet.privateKey}`);
console.log(`Mnemonic: ${JSON.stringify(wallet.mnemonic)}`);
const password = await promptly.prompt("Encryption password: ");
const encryptedJSON = await wallet.encrypt(password);
console.log("Encrypted wallet JSON:");
console.log(encryptedJSON);
});
// You need to export an object to set up your config
// Go to https://hardhat.org/config/ to learn more
/**
* @type import('hardhat/config').HardhatUserConfig
*/
module.exports = {
solidity: "0.8.2",
defaultNetwork: "localhost",
networks: {
hardhat: {
chainId: 1337,
},
rinkeby: {
url: "https://rinkeby.infura.io/v3/2e73045db2e84711912f8d0e5968f309",
accounts: [
process.env.DEPLOY_KEY ||
"0xffb4230bdf9b1f1dd48f0bc54e4007436733f225a4f163d4f7e58e620ae329eb",
],
},
rsk: {
url: "https://rsk-testnet.kosmos.org",
accounts: [
process.env.DEPLOY_KEY ||
"0xffb4230bdf9b1f1dd48f0bc54e4007436733f225a4f163d4f7e58e620ae329eb",
],
},
},
namedAccounts: {
deployer: {
default: 0,
},
},
};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
[{"constant":true,"inputs":[],"name":"ens","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"fac","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"appId","type":"bytes32"}],"name":"latestVersionAppBase","outputs":[{"name":"base","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"appIds","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_fac","type":"address"},{"name":"_ens","type":"address"},{"name":"_appIds","type":"bytes32[4]"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"dao","type":"address"}],"name":"DeployInstance","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"dao","type":"address"},{"indexed":false,"name":"appProxy","type":"address"},{"indexed":false,"name":"appId","type":"bytes32"}],"name":"InstalledApp","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"appProxy","type":"address"},{"indexed":false,"name":"appId","type":"bytes32"}],"name":"InstalledApp","type":"event"},{"constant":false,"inputs":[],"name":"newInstance","outputs":[{"name":"dao","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

8
lib/addresses.json Normal file
View File

@ -0,0 +1,8 @@
{
"1337": {
"Contributor": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
"Contribution": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9",
"Token": "0x0165878A594ca255338adfa4d48449f69242Eb8F",
"Reimbursement": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"
}
}

View File

@ -1,17 +0,0 @@
const Base = require('./base');
const EthersUtils = require('ethers').utils;
class Acl extends Base {
hasPermission (fromAddress, contractAddress, roleID, params = null) {
let roleHash = EthersUtils.keccak256(EthersUtils.toUtf8Bytes(roleID));
return this.hasPermission(
fromAddress,
contractAddress,
roleHash,
params
);
}
}
module.exports = Acl;

View File

@ -14,15 +14,6 @@ class Base {
return this.contract.address;
}
get ipfs () {
if (!this._ipfsAPI) { throw new Error('IPFS API not configured; please set an ipfs instance'); }
return this._ipfsAPI;
}
set ipfs (ipfsAPI) {
this._ipfsAPI = ipfsAPI;
}
on (type, callback) {
return this.contract.on(type, callback);
}

View File

@ -4,6 +4,4 @@ module.exports = {
Proposal: require('./proposal'),
Token: require('./token'),
Reimbursement: require('./reimbursement'),
Kernel: require('./kernel'),
Acl: require('./acl'),
};

View File

@ -1,24 +0,0 @@
const namehash = require('ethers').utils.namehash;
const Base = require('./base');
const KERNEL_APP_ADDR_NAMESPACE = '0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb';
class Kernel extends Base {
constructor (contract) {
super(contract);
this.apm = 'open.aragonpm.eth'; // can be overwritten if needed
}
getApp (appName) {
if (appName === 'Acl') {
return this.contract.acl();
}
return this.contract.getApp(KERNEL_APP_ADDR_NAMESPACE, this.appNamehash(appName));
}
appNamehash (appName) {
return namehash(`kredits-${appName.toLowerCase()}.${this.apm}`);
}
}
module.exports = Kernel;

View File

@ -1,64 +1,66 @@
const Record = require('./record');
const ExpenseSerializer = require('../serializers/expense');
const Record = require("./record");
const ExpenseSerializer = require("../serializers/expense");
class Reimbursement extends Record {
get count () {
return this.functions.reimbursementsCount();
return this.contract.reimbursementsCount();
}
getById (id) {
return this.functions.get(id)
.then(data => {
return this.ipfs.catAndMerge(data, (ipfsDocument) => {
const expenses = JSON.parse(ipfsDocument);
return { expenses };
});
return this.contract.get(id).then((data) => {
return this.ipfs.catAndMerge(data, (ipfsDocument) => {
const expenses = JSON.parse(ipfsDocument);
return { expenses };
});
});
}
getData (id) {
return this.functions.getReimbursement(id);
return this.contract.getReimbursement(id);
}
async add (attrs, callOptions = {}) {
const amount = parseInt(attrs.amount);
const token = attrs.token;
const recipientId = attrs.recipientId;
const expenses = attrs.expenses.map( e => new ExpenseSerializer(e) );
const expenses = attrs.expenses.map((e) => new ExpenseSerializer(e));
let errorMessage;
if (typeof amount !== 'number' || amount <= 0) {
errorMessage = 'Invalid data: amount must be a positive number.';
if (typeof amount !== "number" || amount <= 0) {
errorMessage = "Invalid data: amount must be a positive number.";
}
if (!token || token === '') {
errorMessage = 'Invalid data: token must be a token address.';
if (!token || token === "") {
errorMessage = "Invalid data: token must be a token address.";
}
if (!recipientId || recipientId === '') {
errorMessage = 'Invalid data: recipientId is required.';
if (!recipientId || recipientId === "") {
errorMessage = "Invalid data: recipientId is required.";
}
if (expenses.length === 0) {
errorMessage = 'Invalid data: at least one expense item is required.';
errorMessage = "Invalid data: at least one expense item is required.";
}
if (errorMessage) {
return Promise.reject(new Error(errorMessage));
}
if (errorMessage) { return Promise.reject(new Error(errorMessage)); }
return Promise.all(expenses.map(e => e.validate()))
.then(() => {
const jsonStr = JSON.stringify(expenses.map(e => e.data), null, 2);
return this.ipfs
.add(jsonStr)
.then(ipfsHashAttr => {
const reimbursement = [
amount,
token,
parseInt(recipientId),
ipfsHashAttr.hashDigest,
ipfsHashAttr.hashFunction,
ipfsHashAttr.hashSize,
];
return Promise.all(expenses.map((e) => e.validate())).then(() => {
const jsonStr = JSON.stringify(
expenses.map((e) => e.data),
null,
2
);
return this.ipfs.add(jsonStr).then((ipfsHashAttr) => {
const reimbursement = [
amount,
token,
parseInt(recipientId),
ipfsHashAttr.hashDigest,
ipfsHashAttr.hashFunction,
ipfsHashAttr.hashSize,
];
return this.functions.add(...reimbursement, callOptions);
});
return this.contract.add(...reimbursement, callOptions);
});
});
}
}

View File

@ -8,19 +8,14 @@ const ABIS = {
Contribution: require('./abis/Contribution.json'),
Reimbursement: require('./abis/Reimbursement.json'),
Token: require('./abis/Token.json'),
Proposal: require('./abis/Proposal.json'),
Kernel: require('./abis/Kernel.json'),
Acl: require('./abis/ACL.json'),
};
const APP_CONTRACTS = [
'Contributor',
'Contribution',
'Token',
'Proposal',
'Reimbursement',
'Acl',
];
const DaoAddresses = require('./addresses/dao.json');
// const APP_CONTRACTS = [
// 'Contributor',
// 'Contribution',
// 'Token',
// 'Reimbursement',
// ];
const Addresses = require('./addresses.json');
const Contracts = require('./contracts');
const IPFS = require('./utils/ipfs');
@ -34,7 +29,7 @@ function capitalize (word) {
class Kredits {
constructor (provider, signer, options = {}) {
let { addresses, abis, ipfsConfig } = options;
const { addresses, abis, ipfsConfig } = options;
this.provider = provider;
this.signer = signer;
@ -45,21 +40,15 @@ class Kredits {
this.contracts = {};
}
init (names) {
let contractsToLoad = names || APP_CONTRACTS;
return this.provider.getNetwork().then(network => {
this.addresses['Kernel'] = this.addresses['Kernel'] || DaoAddresses[network.chainId.toString()];
let addressPromises = contractsToLoad.map((contractName) => {
return this.Kernel.getApp(contractName).then((address) => {
this.addresses[contractName] = address;
}).catch((error) => {
throw new Error(`Failed to get address for ${contractName} from DAO at ${this.Kernel.contract.address}
- ${error.message}`
);
});
});
init (/* names */) {
// TODO implement
// const contractsToLoad = names || APP_CONTRACTS;
return Promise.all(addressPromises).then(() => { return this; });
return this.provider.getNetwork().then(network => {
if (Object.keys(this.addresses).length === 0) {
this.addresses = Addresses[network.chainId.toString()];
}
return this;
});
}
@ -86,16 +75,7 @@ class Kredits {
}
static availableNetworks () {
return Object.keys(DaoAddresses);
}
get Kernel () {
let k = this.contractFor('Kernel');
// in case we want to use a special apm (e.g. development vs. production)
if (this.options.apm) {
k.apm = this.options.apm;
}
return k;
return Object.keys(Addresses);
}
get Contributor () {
@ -104,11 +84,7 @@ class Kredits {
get Contributors () {
deprecate('Contributors is deprecated use Contributor instead');
return this.Contributor;
}
get Proposal () {
return this.contractFor('Proposal');
return this.contractFor('Contributor');
}
get Operator () {
@ -127,10 +103,6 @@ class Kredits {
return this.contractFor('Reimbursement');
}
get Acl () {
return this.contractFor('Acl');
}
// Should be private
contractFor (name) {
if (this.contracts[name]) {
@ -144,8 +116,8 @@ class Kredits {
throw new Error(`Address or ABI not found for ${contractName}`);
}
let signerOrProvider = this.signer || this.provider;
let contract = new ethers.Contract(address, abi, signerOrProvider);
const signerOrProvider = this.signer || this.provider;
const contract = new ethers.Contract(address, abi, signerOrProvider);
this.contracts[name] = new Contracts[contractName](contract);
this.contracts[name].ipfs = this.ipfs;

View File

@ -8,10 +8,10 @@ class IPFS {
config = { host: 'localhost', port: '5001', protocol: 'http' };
}
this._config = config;
this._ipfsAPI = ipfsClient(config);
this._ipfsAPI = ipfsClient.create(config);
}
catAndMerge (contractData, deserialize) {
async catAndMerge (contractData, deserialize) {
let data = {...contractData}; // data from ethers.js is not extensible. this copy the attributes in a new object
// if no hash details are found simply return the data; nothing to merge
if (!data.hashSize || data.hashSize === 0) {
@ -22,20 +22,19 @@ class IPFS {
return this.cat(data.ipfsHash)
.then(deserialize)
.then((attributes) => {
.then(attributes => {
return Object.assign({}, data, attributes);
});
}
add (data) {
return this._ipfsAPI
.add(ipfsClient.Buffer.from(data))
.then((res) => {
return this.decodeHash(res[0].hash);
async add (data) {
return this._ipfsAPI.add(data)
.then(res => {
return this.decodeHash(res.path);
});
}
cat (hashData) {
async cat (hashData) {
let ipfsHash = hashData; // default - if it is a string
if (Object.prototype.hasOwnProperty.call(hashData, 'hashSize')) {
ipfsHash = this.encodeHash(hashData);
@ -43,7 +42,12 @@ class IPFS {
if (this._config['gatewayUrl']) {
return fetch(`${this._config['gatewayUrl']}/${ipfsHash}`).then(r => r.text());
} else {
return this._ipfsAPI.cat(ipfsHash);
const res = this._ipfsAPI.cat(ipfsHash);
let str = '';
for await (const buffer of res) {
str += buffer.toString();
}
return Promise.resolve(str);
}
}
@ -66,7 +70,7 @@ class IPFS {
}
encodeHash (hashData) {
let digest = ipfsClient.Buffer.from(hashData.hashDigest.slice(2), 'hex');
const digest = Buffer.from(hashData.hashDigest.slice(2), 'hex');
return multihashes.encode(digest, hashData.hashFunction, hashData.hashSize);
}
}

View File

@ -1,5 +0,0 @@
var Migrations = artifacts.require("./Migrations.sol");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};

35028
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -7,20 +7,17 @@
"test": "test"
},
"scripts": {
"install-all": "./scripts/every-app.sh \"npm install\"",
"wallet:create": "hardhat create-wallet",
"devchain": "hardhat node --network hardhat",
"deploy:dao": "hardhat run scripts/create-proxy.js",
"postshrinkwrap": "node scripts/fix-package-lock.js &>/dev/null || true",
"build-json": "npm run compile-contracts && node ./scripts/build-json.js",
"repl": "truffle exec scripts/repl.js",
"seeds": "truffle exec scripts/seeds.js",
"compile-contracts": "truffle compile --all",
"bootstrap": "npm run reset:hard && npm run seeds",
"reset": "npm run deploy:kit && npm run deploy:dao",
"reset:hard": "npm run compile-contracts && npm run deploy:apps && npm run reset",
"deploy:kit": "truffle exec scripts/deploy-kit.js",
"deploy:dao": "truffle exec scripts/new-dao.js",
"deploy:apps": "./scripts/every-app.sh \"aragon apm publish major --propagate-content=false --build=false --prepublish=false --skip-confirmation\"",
"devchain": "aragon devchain --port 7545",
"dao:address": "truffle exec scripts/current-address.js",
"build": "npm run build:contracts && npm run build:json",
"build:contracts": "hardhat compile --force",
"build:json": "node ./scripts/build-json.js",
"seeds": "hardhat run scripts/seeds.js",
"fund": "hardhat fund",
"bootstrap": "npm run build && npm run deploy:dao && npm run seeds",
"repl": "hardhat console",
"lint:contracts": "solhint \"contracts/**/*.sol\" \"apps/*/contracts/**/*.sol\"",
"lint:contract-tests": "eslint apps/*/test",
"lint:wrapper": "eslint lib/",
@ -33,37 +30,45 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/67P/truffle-kredits.git"
"url": "git+https://github.com/67P/kredits-contracts.git"
},
"author": "",
"license": "MIT",
"bugs": {
"url": "https://github.com/67P/truffle-kredits/issues"
"url": "https://github.com/67P/kredits-contracts/issues"
},
"homepage": "https://github.com/67P/truffle-kredits#readme",
"homepage": "https://github.com/67P/kredits-contracts#readme",
"devDependencies": {
"@aragon/kits-base": "^1.0.0",
"@aragon/os": "^4.4.0",
"@nomiclabs/hardhat-ethers": "^2.0.2",
"@nomiclabs/hardhat-waffle": "^2.0.1",
"@openzeppelin/contracts-upgradeable": "^4.3.2",
"@openzeppelin/hardhat-upgrades": "^1.10.0",
"async-each-series": "^1.1.0",
"cli-table": "^0.3.1",
"eslint": "^7.1.0",
"colors": "^1.0.3",
"eslint": "^8.14.0",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eth-provider": "^0.2.5",
"eslint-plugin-promise": "^6.0.0",
"eth-provider": "^0.11.0",
"ethereum-block-by-date": "^1.4.0",
"ethereum-waffle": "^3.4.0",
"hardhat": "^2.6.4",
"hardhat-deploy": "^0.11.4",
"hardhat-deploy-ethers": "^0.3.0-beta.10",
"homedir": "^0.6.0",
"promptly": "^3.0.3",
"solc": "^0.6.8",
"solhint": "^2.3.1",
"solhint": "^3.3.7",
"truffle-hdwallet-provider": "^1.0.17",
"truffle-hdwallet-provider-privkey": "^0.3.0",
"web3-providers-ws": "^1.7.3",
"yargs": "^15.0.0"
},
"dependencies": {
"ethers": "^5.0.2",
"ipfs-http-client": "^41.0.1",
"@kosmos/schemas": "^3.0.0",
"@kosmos/schemas": "^3.1.0",
"ethers": "^5.4.7",
"ipfs-http-client": "^56.0.3",
"multihashes": "^4.0.3",
"node-fetch": "^2.6.0",
"tv4": "^1.3.0"
},

View File

@ -1,16 +1,12 @@
const promptly = require('promptly');
const { inspect } = require('util');
const initKredits = require('./helpers/init_kredits.js');
const { ethers } = require("hardhat");
const Kredits = require('../lib/kredits');
module.exports = async function(callback) {
let kredits;
try {
kredits = await initKredits(web3);
} catch(e) {
callback(e);
return;
}
async function main() {
kredits = new Kredits(hre.ethers.provider, hre.ethers.provider.getSigner())
await kredits.init();
console.log(`Using Contributions at: ${kredits.Contribution.contract.address}`);
@ -49,10 +45,11 @@ module.exports = async function(callback) {
.then(result => {
console.log("\n\nResult:");
console.log(result);
callback();
})
.catch(error => {
console.log('Failed to create contribution');
callback(inspect(error));
console.log(error);
});
}
main();

View File

@ -1,6 +1,7 @@
const promptly = require('promptly');
const initKredits = require('./helpers/init_kredits.js');
const { ethers } = require("hardhat");
const Kredits = require('../lib/kredits');
async function prompt(message, options) {
if (!options) {
@ -8,15 +9,9 @@ async function prompt(message, options) {
}
return await promptly.prompt(message, options);
}
module.exports = async function(callback) {
let kredits;
try {
kredits = await initKredits(web3);
} catch(e) {
callback(e);
return;
}
async function main() {
kredits = new Kredits(hre.ethers.provider, hre.ethers.provider.getSigner())
await kredits.init();
console.log(`Using contributors at: ${kredits.Contributor.contract.address}`);
@ -36,9 +31,10 @@ module.exports = async function(callback) {
kredits.Contributor.add(contributorAttributes, { gasLimit: 350000 }).then((result) => {
console.log("\n\nResult:");
console.log(result);
callback();
}).catch((error) => {
console.log('Failed to create contributor');
callback(error);
console.log(error);
});
}
main();

View File

@ -1,56 +0,0 @@
const promptly = require('promptly');
const { inspect } = require('util');
const initKredits = require('./helpers/init_kredits.js');
module.exports = async function(callback) {
let kredits;
try {
kredits = await initKredits(web3);
} catch(e) {
callback(e);
return;
}
console.log(`Using Proposal at: ${kredits.Proposal.contract.address}`);
let contributor = await promptly.prompt('Contributor (address or id): ');
let contributorId;
let contributorAccount;
if (contributor.length < 5) {
contributorId = contributor;
contributorAccount = await kredits.Contributor.contract.getContributorAddressById(contributor);
} else {
contributorAccount = contributor;
contributorId = await kredits.Contributor.contract.getContributorIdByAddress(contributor);
}
console.log(`Creating a proposal for contributor ID #${contributorId} account: ${contributorAccount}`);
[ dateNow, timeNow ] = (new Date()).toISOString().split('T');
let contributionAttributes = {
contributorId,
date: dateNow,
time: timeNow,
amount: await promptly.prompt('Amount: '),
description: await promptly.prompt('Description: '),
kind: await promptly.prompt('Kind: ', { default: 'dev' }),
url: await promptly.prompt('URL: ', { default: '' })
}
const contributorData = await kredits.Contributor.getById(contributorId);
contributionAttributes.contributorIpfsHash = contributorData.ipfsHash;
console.log("\nAdding proposal:");
console.log(contributionAttributes);
kredits.Proposal.addProposal(contributionAttributes, { gasLimit: 300000 })
.then((result) => {
console.log("\n\nResult:");
console.log(result);
callback();
}).catch((error) => {
console.log('Failed to create proposal');
callback(inspect(error));
});
}

View File

@ -1,24 +1,18 @@
const fs = require('fs');
const path = require('path');
const fs = require("fs");
const path = require("path");
const contractsPath = path.join(__dirname, '..', 'build', 'contracts');
const libPath = path.join(__dirname, '..', 'lib');
const abisPath = path.join(libPath, 'abis');
const contractsPath = path.join(__dirname, "..", "artifacts", "contracts");
const libPath = path.join(__dirname, "..", "lib");
const abisPath = path.join(libPath, "abis");
const files = [
'Contributor',
'Contribution',
'Kernel',
'Proposal',
'Token',
'Reimbursement',
'ACL'
];
const files = ["Contributor", "Contribution", "Token", "Reimbursement"];
files.forEach((fileName) => {
let file = require(`${contractsPath}/${fileName}.json`);
let file = require(`${contractsPath}/${fileName}.sol/${fileName}.json`);
let abiFile = path.join(abisPath, `${fileName}.json`);
fs.writeFileSync(abiFile, JSON.stringify(file.abi));
});
console.log("Don't forget to reaload the JSON files from your application; i.e. restart kredits-web");
console.log(
"Don't forget to reaload the JSON files from your application; i.e. restart kredits-web"
);

View File

@ -1,53 +0,0 @@
const promptly = require('promptly');
const Table = require('cli-table');
const initKredits = require('./helpers/init_kredits.js');
module.exports = async function(callback) {
let kredits;
try {
kredits = await initKredits(web3);
} catch(e) {
callback(e);
return;
}
console.log(`Using Contribution at: ${kredits.Contribution.contract.address}`);
let recipient = await promptly.prompt('Contributor ID: ');
recipient = parseInt(recipient);
const table = new Table({
head: ['ID', 'Description', 'Amount', 'Claim Transaction'],
});
try {
let blockNumber = await kredits.provider.getBlockNumber();
let contributions = await kredits.Contribution.all({page: {size: 200}});
console.log(`Current block number: ${blockNumber}`);
let claimPromises = contributions.map(async (c) => {
const confirmed = c.confirmedAtBlock <= blockNumber;
if (c.contributorId === recipient && confirmed && !c.vetoed && !c.claimed) {
console.log(`Claiming contribution ID=${c.id}`);
return kredits.Contribution.contract.claim(c.id, { gasLimit: 500000 }).then(tx => {
table.push([
c.id.toString(),
`${c.description}`,
c.amount.toString(),
tx.hash,
]);
});
}
});
Promise.all(claimPromises).then(_ => {
console.log(table.toString());
callback();
});
} catch (err) {
console.log(err);
callback();
}
};

View File

@ -1,61 +0,0 @@
const REPL = require('repl');
const promptly = require('promptly');
const initKredits = require('./helpers/init_kredits.js');
module.exports = async function(callback) {
initKredits(web3).then(async function(kredits) {
let contractName = await promptly.prompt('Contract Name: ');
const contractWrapper = kredits[contractName];
let method;
method = await promptly.prompt('Function (? for available functions): ');
while (method === '?') {
console.log(`Contract functions: ${JSON.stringify(Object.keys(contractWrapper.functions))}`);
console.log(`\nWrapper functions: ${JSON.stringify(Object.getOwnPropertyNames(Object.getPrototypeOf(contractWrapper)))}`);
console.log("\n");
method = await promptly.prompt('Function: ');
}
if (!contractWrapper[method] && !contractWrapper.contract[method]) {
callback(new Error(`Method ${method} is not defined on ${contractName}`));
return;
}
let argumentInput = await promptly.prompt('Arguments (comma separated): ', { default: '' });
let args = [];
if (argumentInput !== '') {
args = argumentInput.split(',').map(a => a.trim());
}
console.log(`Using ${contractName} at ${contractWrapper.contract.address}`);
console.log(`Calling ${method} with ${JSON.stringify(args)}`);
let func;
if (contractWrapper[method]) {
func = contractWrapper[method];
} else {
func = contractWrapper.contract[method];
}
func.apply(contractWrapper, args).then((result) => {
console.log("\nResult:");
console.log(result);
console.log("\nStartig a REPL. (type .exit to exit)");
console.log(`defined variables: result, ${contractName}, kredis`);
let r = REPL.start();
r.context.result = result;
r.context[contractName] = contractWrapper;
r.context.kredits = kredits;
r.on('exit', () => {
console.log('Bye');
callback();
})
}).catch((error) => {
console.log("Call failed. Probably the contract raised an error?\n");
console.log("...");
callback(error);
});
})
}

78
scripts/create-proxy.js Normal file
View File

@ -0,0 +1,78 @@
const { ethers, upgrades } = require("hardhat");
const path = require("path");
const fileInject = require("./helpers/file_inject.js");
async function main() {
const network = await hre.ethers.provider.getNetwork();
const networkId = network.chainId;
console.log(`Deploying to network #${networkId}`);
const Contributor = await ethers.getContractFactory("Contributor");
const Contribution = await ethers.getContractFactory("Contribution");
const Token = await ethers.getContractFactory("Token");
const Reimbursement = await ethers.getContractFactory("Reimbursement");
const contributor = await upgrades.deployProxy(Contributor, []);
await contributor.deployed();
console.log("Contributor deployed to:", contributor.address);
console.log("...waiting for 1 confirmation");
await contributor.deployTransaction.wait();
const blocksToWait = 40320; // 7 days; 15 seconds block time
const contribution = await upgrades.deployProxy(Contribution, [blocksToWait]);
await contribution.deployed();
console.log("Contribution deployed to:", contribution.address);
console.log("...waiting for 1 confirmation");
await contribution.deployTransaction.wait();
const token = await upgrades.deployProxy(Token, []);
await token.deployed();
console.log("Token deployed to:", token.address);
console.log("...waiting for 1 confirmation");
await token.deployTransaction.wait();
const reimbursement = await upgrades.deployProxy(Reimbursement, []);
await reimbursement.deployed();
console.log("Reimbursement deployed to:", reimbursement.address);
console.log("...waiting for 1 confirmation");
await reimbursement.deployTransaction.wait();
await contributor
.setTokenContract(token.address)
.then((response) => response.wait());
await contributor
.setContributionContract(contribution.address)
.then((response) => response.wait());
await contribution
.setTokenContract(token.address)
.then((response) => response.wait());
await contribution
.setContributorContract(contributor.address)
.then((response) => response.wait());
await token
.setContributionContract(contribution.address)
.then((response) => response.wait());
await token
.setContributorContract(contributor.address)
.then((response) => response.wait());
await reimbursement
.setContributorContract(contributor.address)
.then((response) => response.wait());
const addresses = {
Contributor: contributor.address,
Contribution: contribution.address,
Token: token.address,
Reimbursement: reimbursement.address,
};
console.log("Writing addresses.json");
const libPath = path.join(__dirname, "..", "lib");
fileInject(path.join(libPath, "addresses.json"), networkId, addresses);
console.log("DONE!");
}
main();

Some files were not shown because too many files have changed in this diff Show More