commit
683ec5b2e9
6
.gitignore
vendored
6
.gitignore
vendored
@ -1,8 +1,10 @@
|
|||||||
build
|
|
||||||
flattened_contracts
|
|
||||||
node_modules
|
node_modules
|
||||||
**/node_modules
|
**/node_modules
|
||||||
.ganache-db
|
.ganache-db
|
||||||
.tm_properties
|
.tm_properties
|
||||||
yarn-error.log
|
yarn-error.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
cache
|
||||||
|
artifacts
|
||||||
|
.openzeppelin
|
||||||
|
207
README.md
207
README.md
@ -2,13 +2,8 @@
|
|||||||
|
|
||||||
# Kredits Contracts
|
# Kredits Contracts
|
||||||
|
|
||||||
This repository contains the Solidity smart contracts organized as
|
This repository contains the Solidity smart contracts and the JavaScript API
|
||||||
[Aragon](https://hack.aragon.org/) apps and JavaScript API wrapper for [Kosmos
|
wrapper for [Kosmos Kredits](https://wiki.kosmos.org/Kredits).
|
||||||
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.
|
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
|
|
||||||
@ -20,224 +15,108 @@ All requirements are defined in `package.json`.
|
|||||||
|
|
||||||
$ npm install
|
$ 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
|
### Local development chain
|
||||||
|
|
||||||
For local development it is recommended to use
|
We use [hardhat](https://hardhat.org/) as development environment for the
|
||||||
[ganache](http://truffleframework.com/ganache/) to run a local development
|
smart contracts.
|
||||||
chain. When using the ganache simulator, no full Ethereum node is required.
|
|
||||||
|
|
||||||
We use the default aragon-cli devchain command to configure and run a local
|
To run a local development chain run:
|
||||||
development ganache.
|
|
||||||
|
|
||||||
$ npm run devchain (or aragon devchain --port 7545)
|
$ npm run devchain # or: hardhat node --network hardhat
|
||||||
|
|
||||||
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`.
|
|
||||||
|
|
||||||
### Bootstrap
|
### Bootstrap
|
||||||
|
|
||||||
1. Run an Ethereum node and ipfs
|
1. Run an Ethereum node and ipfs
|
||||||
|
|
||||||
$ npm run devchain
|
$ npm run devchain
|
||||||
$ ipfs daemon
|
$ ipfs daemon
|
||||||
|
|
||||||
2. Compile contracts
|
2. Compile contracts and build ABIs
|
||||||
|
|
||||||
(compiled contracts will be in `/build`)
|
(compiled artifacts will be in `/artifacts`)
|
||||||
$ npm run compile-contracts
|
$ 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
|
$ 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
|
$ 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
|
$ cat lib/addresses.json
|
||||||
$ npm run reset:hard // deploys all apps and does reset
|
|
||||||
|
## 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
|
## Contract architecture
|
||||||
|
|
||||||
Contracts are organized in independent apps (see `/apps`) and are developed and
|
We use the [OpenZeppelin hardhat
|
||||||
deployed independently. Each app has a version and can be "installed" on the
|
proxy](https://www.npmjs.com/package/@openzeppelin/hardhat-upgrades) for
|
||||||
Kredits DAO independently.
|
deploying and managing upgradeable contracts. (see `scripts/create-proxy.js`)
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
|
Each contract is independent and is connected to its dependencies by storing
|
||||||
|
the addresses of the other contracts.
|
||||||
|
|
||||||
## Helper scripts
|
## Helper scripts
|
||||||
|
|
||||||
`scripts/` contains some helper scripts to interact with the contracts from the
|
`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._
|
CLI. _At some point these should be moved into a real nice CLI._
|
||||||
|
|
||||||
To run these scripts use `truffle exec`. For example: `truffle exec
|
To run these scripts use `hardhat run`. For example: `hardhat run
|
||||||
scripts/add-proposal.js`.
|
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
|
### repl/console
|
||||||
|
|
||||||
Call any function on any contract:
|
|
||||||
|
|
||||||
$ truffle exec scripts/cli.js
|
|
||||||
|
|
||||||
### repl.js
|
|
||||||
|
|
||||||
Similar to cli.js but only provides a REPL with an initialized `kredits`
|
Similar to cli.js but only provides a REPL with an initialized `kredits`
|
||||||
instance.
|
instance.
|
||||||
|
|
||||||
$ truffle exec scripts/repl.js
|
$ hardhat console --network localhost
|
||||||
|
|
||||||
### add-{contributor, contribution, proposal}.js
|
### add-{contributor, contribution, proposal}.js
|
||||||
|
|
||||||
Script to add a new entries to the contracts using the JS wrapper
|
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-{contributors, contributions, proposals}.js
|
||||||
|
|
||||||
List contract entries
|
List contract entries
|
||||||
|
|
||||||
$ truffle exec scripts/list-{contributors, contributions, proposals}.js
|
$ hardhat run scripts/list-{contributors, contributions, proposals}.js --network localhost
|
||||||
|
|
||||||
### 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
|
|
||||||
|
|
||||||
### seeds.js
|
### seeds.js
|
||||||
|
|
||||||
Run seeds defined in `config/seeds.js`.
|
Run seeds defined in `config/seeds.js`.
|
||||||
|
|
||||||
$ truffle exec scripts/seeds.js
|
|
||||||
or
|
|
||||||
$ npm run seeds
|
$ 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
|
$ cat lib/addresses.json
|
||||||
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
|
|
||||||
|
|
||||||
## Upgradeable contracts
|
## Upgradeable contracts
|
||||||
|
|
||||||
We use aragonOS for upgradeability of the different contracts. Refer to the
|
We use OpenZeppelin for an upgradeable contracts:
|
||||||
[aragonOS upgradeablity documentation](https://hack.aragon.org/docs/upgradeability-intro)
|
[https://www.npmjs.com/package/@openzeppelin/hardhat-upgrades](https://www.npmjs.com/package/@openzeppelin/hardhat-upgrades)
|
||||||
for more details.
|
|
||||||
|
|
||||||
### Example
|
Refer to the OpenZeppelin README and `scripts/create-proxy.js`
|
||||||
|
|
||||||
1. Setup (see #Bootstrap)
|
[OpenZeppelin Step by Step guide](https://forum.openzeppelin.com/t/openzeppelin-upgrades-step-by-step-tutorial-for-hardhat/3580)
|
||||||
1. Deploy each contract/apps (see `/apps/*`)
|
|
||||||
2. Create a new DAO (see scripts/deploy-kit.js)
|
For an upgrade example checkout `scripts/upgrade-example.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>`)
|
|
||||||
|
|
||||||
## Known Issues
|
## Known Issues
|
||||||
|
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
'globals': {
|
|
||||||
contract: true,
|
|
||||||
describe: true,
|
|
||||||
it: true,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
'no-unused-vars': ['error', {
|
|
||||||
'argsIgnorePattern': '^_',
|
|
||||||
}],
|
|
||||||
}
|
|
||||||
}
|
|
4
apps/contribution/.gitignore
vendored
4
apps/contribution/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
node_modules
|
|
||||||
build
|
|
||||||
.cache
|
|
||||||
dist
|
|
@ -1,14 +0,0 @@
|
|||||||
# Git files
|
|
||||||
.gitignore
|
|
||||||
|
|
||||||
# Build files
|
|
||||||
.cache
|
|
||||||
node_modules
|
|
||||||
build
|
|
||||||
|
|
||||||
# Lock files
|
|
||||||
package-lock.json
|
|
||||||
yarn.lock
|
|
||||||
|
|
||||||
# Others
|
|
||||||
test
|
|
@ -1 +0,0 @@
|
|||||||
# Kredits Contribution
|
|
@ -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"
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Contribution",
|
|
||||||
"description": "Kredits contribution app"
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
var Migrations = artifacts.require('./Migrations.sol')
|
|
||||||
|
|
||||||
module.exports = function (deployer) {
|
|
||||||
deployer.deploy(Migrations)
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
var Contribution = artifacts.require('Contribution.sol')
|
|
||||||
|
|
||||||
module.exports = function (deployer) {
|
|
||||||
deployer.deploy(Contribution)
|
|
||||||
}
|
|
7900
apps/contribution/package-lock.json
generated
7900
apps/contribution/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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": []
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
// const Contribution = artifacts.require('Contribution.sol');
|
|
||||||
|
|
||||||
contract('Contribution', (_accounts) => {
|
|
||||||
it('should be tested');
|
|
||||||
});
|
|
@ -1 +0,0 @@
|
|||||||
module.exports = require("../../truffle.js");
|
|
4
apps/contributor/.gitignore
vendored
4
apps/contributor/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
node_modules
|
|
||||||
build
|
|
||||||
.cache
|
|
||||||
dist
|
|
@ -1,14 +0,0 @@
|
|||||||
# Git files
|
|
||||||
.gitignore
|
|
||||||
|
|
||||||
# Build files
|
|
||||||
.cache
|
|
||||||
node_modules
|
|
||||||
build
|
|
||||||
|
|
||||||
# Lock files
|
|
||||||
package-lock.json
|
|
||||||
yarn.lock
|
|
||||||
|
|
||||||
# Others
|
|
||||||
test
|
|
@ -1 +0,0 @@
|
|||||||
# Kredits Contributor
|
|
@ -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"
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 {
|
|
||||||
// ...
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Contributor",
|
|
||||||
"description": "Kredits Contributor app"
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
var Migrations = artifacts.require('./Migrations.sol')
|
|
||||||
|
|
||||||
module.exports = function (deployer) {
|
|
||||||
deployer.deploy(Migrations)
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
var Contributor = artifacts.require('Contributor.sol')
|
|
||||||
|
|
||||||
module.exports = function (deployer) {
|
|
||||||
deployer.deploy(Contributor)
|
|
||||||
}
|
|
7900
apps/contributor/package-lock.json
generated
7900
apps/contributor/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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": []
|
|
||||||
}
|
|
@ -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';
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
@ -1 +0,0 @@
|
|||||||
module.exports = require("../../truffle.js");
|
|
@ -1 +0,0 @@
|
|||||||
# Kredits Proposal
|
|
@ -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"
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Proposal",
|
|
||||||
"description": "Kredits Proposal app"
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
var Migrations = artifacts.require('./Migrations.sol')
|
|
||||||
|
|
||||||
module.exports = function (deployer) {
|
|
||||||
deployer.deploy(Migrations)
|
|
||||||
}
|
|
7900
apps/proposal/package-lock.json
generated
7900
apps/proposal/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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": []
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
// const Proposal = artifacts.require('Proposal.sol');
|
|
||||||
|
|
||||||
contract('Proposal', (_accounts) => {
|
|
||||||
it('should be tested');
|
|
||||||
});
|
|
@ -1 +0,0 @@
|
|||||||
module.exports = require("../../truffle.js");
|
|
1
apps/reimbursement/.gitattributes
vendored
1
apps/reimbursement/.gitattributes
vendored
@ -1 +0,0 @@
|
|||||||
*.sol linguist-language=Solidity
|
|
7
apps/reimbursement/.gitignore
vendored
7
apps/reimbursement/.gitignore
vendored
@ -1,7 +0,0 @@
|
|||||||
node_modules
|
|
||||||
artifacts
|
|
||||||
.cache
|
|
||||||
cache
|
|
||||||
dist
|
|
||||||
ipfs.cmd
|
|
||||||
package-lock.json
|
|
@ -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"
|
|
||||||
}
|
|
@ -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
|
|
||||||
},
|
|
||||||
}
|
|
@ -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"
|
|
||||||
}
|
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -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,
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
||||||
*/
|
|
@ -1 +0,0 @@
|
|||||||
module.exports = require("../../truffle.js");
|
|
File diff suppressed because it is too large
Load Diff
4
apps/token/.gitignore
vendored
4
apps/token/.gitignore
vendored
@ -1,4 +0,0 @@
|
|||||||
node_modules
|
|
||||||
build
|
|
||||||
.cache
|
|
||||||
dist
|
|
@ -1,14 +0,0 @@
|
|||||||
# Git files
|
|
||||||
.gitignore
|
|
||||||
|
|
||||||
# Build files
|
|
||||||
.cache
|
|
||||||
node_modules
|
|
||||||
build
|
|
||||||
|
|
||||||
# Lock files
|
|
||||||
package-lock.json
|
|
||||||
yarn.lock
|
|
||||||
|
|
||||||
# Others
|
|
||||||
test
|
|
@ -1 +0,0 @@
|
|||||||
# Kredits Token
|
|
@ -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"
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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 {
|
|
||||||
// ...
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "Token",
|
|
||||||
"description": "Kredits Token app"
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
var Migrations = artifacts.require('./Migrations.sol')
|
|
||||||
|
|
||||||
module.exports = function (deployer) {
|
|
||||||
deployer.deploy(Migrations)
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
var Token = artifacts.require('Token.sol')
|
|
||||||
|
|
||||||
module.exports = function (deployer) {
|
|
||||||
deployer.deploy(Token)
|
|
||||||
}
|
|
7900
apps/token/package-lock.json
generated
7900
apps/token/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -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": []
|
|
||||||
}
|
|
@ -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
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
@ -1 +0,0 @@
|
|||||||
module.exports = require("../../truffle.js");
|
|
59
arapp.json
59
arapp.json
@ -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"
|
|
||||||
}
|
|
@ -1,28 +1,22 @@
|
|||||||
pragma solidity ^0.4.24;
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
import "@aragon/os/contracts/apps/AragonApp.sol";
|
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||||
import "@aragon/os/contracts/kernel/IKernel.sol";
|
|
||||||
|
|
||||||
interface IToken {
|
interface IToken {
|
||||||
function mintFor(address contributorAccount, uint256 amount, uint32 contributionId) public;
|
function mintFor(address contributorAccount, uint256 amount, uint32 contributionId) external;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ContributorInterface {
|
interface ContributorInterface {
|
||||||
function getContributorAddressById(uint32 contributorId) public view returns (address);
|
function getContributorAddressById(uint32 contributorId) external view returns (address);
|
||||||
function getContributorIdByAddress(address contributorAccount) public view returns (uint32);
|
function getContributorIdByAddress(address contributorAccount) external view returns (uint32);
|
||||||
|
function addressIsCore(address sender) external view returns (bool);
|
||||||
// TODO Maybe use for validation
|
// TODO Maybe use for validation
|
||||||
// function exists(uint32 contributorId) public view returns (bool);
|
// function exists(uint32 contributorId) public view returns (bool);
|
||||||
}
|
}
|
||||||
|
|
||||||
contract Contribution is AragonApp {
|
contract Contribution is Initializable {
|
||||||
bytes32 public constant ADD_CONTRIBUTION_ROLE = keccak256("ADD_CONTRIBUTION_ROLE");
|
ContributorInterface public contributorContract;
|
||||||
bytes32 public constant VETO_CONTRIBUTION_ROLE = keccak256("VETO_CONTRIBUTION_ROLE");
|
IToken public tokenContract;
|
||||||
|
|
||||||
bytes32 public constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb;
|
|
||||||
|
|
||||||
// ensure alphabetic order
|
|
||||||
enum Apps { Contribution, Contributor, Proposal, Reimbursement, Token }
|
|
||||||
bytes32[5] public appIds;
|
|
||||||
|
|
||||||
struct ContributionData {
|
struct ContributionData {
|
||||||
uint32 contributorId;
|
uint32 contributorId;
|
||||||
@ -54,36 +48,42 @@ contract Contribution is AragonApp {
|
|||||||
event ContributionClaimed(uint32 id, uint32 indexed contributorId, uint32 amount);
|
event ContributionClaimed(uint32 id, uint32 indexed contributorId, uint32 amount);
|
||||||
event ContributionVetoed(uint32 id, address vetoedByAccount);
|
event ContributionVetoed(uint32 id, address vetoedByAccount);
|
||||||
|
|
||||||
function initialize(bytes32[5] _appIds) public onlyInit {
|
modifier onlyCore {
|
||||||
appIds = _appIds;
|
require(contributorContract.addressIsCore(tx.origin), "Core only");
|
||||||
blocksToWait = 40320; // 7 days; 15 seconds block time
|
_;
|
||||||
initialized();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContract(uint8 appId) public view returns (address) {
|
function initialize(uint32 blocksToWait_) public initializer {
|
||||||
IKernel k = IKernel(kernel());
|
blocksToWait = blocksToWait_;
|
||||||
return k.getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[appId]);
|
}
|
||||||
|
|
||||||
|
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) {
|
function getContributorIdByAddress(address contributorAccount) public view returns (uint32) {
|
||||||
address contributorContract = getContract(uint8(Apps.Contributor));
|
return contributorContract.getContributorIdByAddress(contributorAccount);
|
||||||
return ContributorInterface(contributorContract).getContributorIdByAddress(contributorAccount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContributorAddressById(uint32 contributorId) public view returns (address) {
|
function getContributorAddressById(uint32 contributorId) public view returns (address) {
|
||||||
address contributorContract = getContract(uint8(Apps.Contributor));
|
return contributorContract.getContributorAddressById(contributorId);
|
||||||
return ContributorInterface(contributorContract).getContributorAddressById(contributorId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// Token standard functions (ERC 721)
|
// Token standard functions (ERC 721)
|
||||||
//
|
//
|
||||||
|
|
||||||
function name() external view returns (string) {
|
function name() external view returns (string memory) {
|
||||||
return name_;
|
return name_;
|
||||||
}
|
}
|
||||||
|
|
||||||
function symbol() external view returns (string) {
|
function symbol() external view returns (string memory) {
|
||||||
return symbol_;
|
return symbol_;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ contract Contribution is AragonApp {
|
|||||||
return ownedContributions[contributorId][index];
|
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;
|
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(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;
|
uint32 contributionId = contributionsCount + 1;
|
||||||
ContributionData storage c = contributions[contributionId];
|
ContributionData storage c = contributions[contributionId];
|
||||||
c.exists = true;
|
c.exists = true;
|
||||||
@ -175,7 +176,8 @@ contract Contribution is AragonApp {
|
|||||||
emit ContributionAdded(contributionId, contributorId, amount);
|
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];
|
ContributionData storage c = contributions[contributionId];
|
||||||
require(c.exists, 'NOT_FOUND');
|
require(c.exists, 'NOT_FOUND');
|
||||||
require(!c.claimed, 'ALREADY_CLAIMED');
|
require(!c.claimed, 'ALREADY_CLAIMED');
|
||||||
@ -185,7 +187,7 @@ contract Contribution is AragonApp {
|
|||||||
emit ContributionVetoed(contributionId, msg.sender);
|
emit ContributionVetoed(contributionId, msg.sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
function claim(uint32 contributionId) public isInitialized {
|
function claim(uint32 contributionId) public {
|
||||||
ContributionData storage c = contributions[contributionId];
|
ContributionData storage c = contributions[contributionId];
|
||||||
require(c.exists, 'NOT_FOUND');
|
require(c.exists, 'NOT_FOUND');
|
||||||
require(!c.claimed, 'ALREADY_CLAIMED');
|
require(!c.claimed, 'ALREADY_CLAIMED');
|
||||||
@ -193,14 +195,14 @@ contract Contribution is AragonApp {
|
|||||||
require(block.number >= c.confirmedAtBlock, 'NOT_CLAIMABLE');
|
require(block.number >= c.confirmedAtBlock, 'NOT_CLAIMABLE');
|
||||||
|
|
||||||
c.claimed = true;
|
c.claimed = true;
|
||||||
address tokenContract = getContract(uint8(Apps.Token));
|
|
||||||
address contributorAccount = getContributorAddressById(c.contributorId);
|
address contributorAccount = getContributorAddressById(c.contributorId);
|
||||||
uint256 amount = uint256(c.amount);
|
uint256 amount = uint256(c.amount);
|
||||||
IToken(tokenContract).mintFor(contributorAccount, amount, contributionId);
|
tokenContract.mintFor(contributorAccount, amount, contributionId);
|
||||||
emit ContributionClaimed(contributionId, c.contributorId, c.amount);
|
emit ContributionClaimed(contributionId, c.contributorId, c.amount);
|
||||||
}
|
}
|
||||||
|
|
||||||
function exists(uint32 contributionId) public view returns (bool) {
|
function exists(uint32 contributionId) public view returns (bool) {
|
||||||
return contributions[contributionId].exists;
|
return contributions[contributionId].exists;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,19 +1,19 @@
|
|||||||
pragma solidity ^0.4.24;
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
import "@aragon/os/contracts/apps/AragonApp.sol";
|
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||||
import "@aragon/os/contracts/kernel/IKernel.sol";
|
|
||||||
|
|
||||||
interface ITokenBalance {
|
interface ITokenBalance {
|
||||||
function balanceOf(address contributorAccount) public view returns (uint256);
|
function balanceOf(address contributorAccount) external view returns (uint256);
|
||||||
}
|
}
|
||||||
interface IContributionBalance {
|
interface IContributionBalance {
|
||||||
function totalKreditsEarnedByContributor(uint32 contributorId, bool confirmedOnly) public view returns (uint32 amount);
|
function totalKreditsEarnedByContributor(uint32 contributorId, bool confirmedOnly) external view returns (uint32 amount);
|
||||||
function balanceOf(address owner) public view returns (uint256);
|
function balanceOf(address owner) external view returns (uint256);
|
||||||
}
|
}
|
||||||
|
|
||||||
contract Contributor is AragonApp {
|
contract Contributor is Initializable {
|
||||||
bytes32 public constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb;
|
address deployer;
|
||||||
bytes32 public constant MANAGE_CONTRIBUTORS_ROLE = keccak256("MANAGE_CONTRIBUTORS_ROLE");
|
IContributionBalance public contributionContract;
|
||||||
|
ITokenBalance public tokenContract;
|
||||||
|
|
||||||
struct Contributor {
|
struct Contributor {
|
||||||
address account;
|
address account;
|
||||||
@ -27,23 +27,27 @@ contract Contributor is AragonApp {
|
|||||||
mapping (uint32 => Contributor) public contributors;
|
mapping (uint32 => Contributor) public contributors;
|
||||||
uint32 public contributorsCount;
|
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 ContributorProfileUpdated(uint32 id, bytes32 oldHashDigest, bytes32 newHashDigest); // what should be logged
|
||||||
event ContributorAccountUpdated(uint32 id, address oldAccount, address newAccount);
|
event ContributorAccountUpdated(uint32 id, address oldAccount, address newAccount);
|
||||||
event ContributorAdded(uint32 id, address account);
|
event ContributorAdded(uint32 id, address account);
|
||||||
|
|
||||||
function initialize(address root, bytes32[5] _appIds) public onlyInit {
|
modifier onlyCore {
|
||||||
appIds = _appIds;
|
require(addressIsCore(tx.origin), "Core only");
|
||||||
|
_;
|
||||||
initialized();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function getContract(uint8 appId) public view returns (address) {
|
function initialize() public initializer {
|
||||||
IKernel k = IKernel(kernel());
|
deployer = msg.sender;
|
||||||
return k.getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[appId]);
|
}
|
||||||
|
|
||||||
|
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) {
|
function coreContributorsCount() public view returns (uint32) {
|
||||||
@ -56,7 +60,7 @@ contract Contributor is AragonApp {
|
|||||||
return count;
|
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(newAccount != address(0), "invalid new account address");
|
||||||
require(getContributorAddressById(id) == oldAccount, "contributor does not exist");
|
require(getContributorAddressById(id) == oldAccount, "contributor does not exist");
|
||||||
|
|
||||||
@ -66,7 +70,7 @@ contract Contributor is AragonApp {
|
|||||||
emit ContributorAccountUpdated(id, oldAccount, newAccount);
|
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];
|
Contributor storage c = contributors[id];
|
||||||
bytes32 oldHashDigest = c.hashDigest;
|
bytes32 oldHashDigest = c.hashDigest;
|
||||||
c.hashDigest = hashDigest;
|
c.hashDigest = hashDigest;
|
||||||
@ -76,7 +80,7 @@ contract Contributor is AragonApp {
|
|||||||
ContributorProfileUpdated(id, oldHashDigest, c.hashDigest);
|
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));
|
require(!addressExists(account));
|
||||||
uint32 _id = contributorsCount + 1;
|
uint32 _id = contributorsCount + 1;
|
||||||
assert(!contributors[_id].exists); // this can not be acually
|
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) {
|
function isCoreTeam(uint32 id) view public returns (bool) {
|
||||||
// TODO: for simplicity we simply define the first contributors as core
|
// TODO: for simplicity we simply define the first contributors as core
|
||||||
// later this needs to be changed to something more dynamic
|
// 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) {
|
function exists(uint32 id) view public returns (bool) {
|
||||||
@ -103,6 +107,10 @@ contract Contributor is AragonApp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function addressIsCore(address account) view public returns (bool) {
|
function addressIsCore(address account) view public returns (bool) {
|
||||||
|
// the deployer is always core
|
||||||
|
if(account == deployer) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
uint32 id = getContributorIdByAddress(account);
|
uint32 id = getContributorIdByAddress(account);
|
||||||
return isCoreTeam(id);
|
return isCoreTeam(id);
|
||||||
}
|
}
|
||||||
@ -119,7 +127,7 @@ contract Contributor is AragonApp {
|
|||||||
return contributors[id].account;
|
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];
|
uint32 id = contributorIds[account];
|
||||||
return contributors[id];
|
return contributors[id];
|
||||||
}
|
}
|
||||||
@ -132,24 +140,10 @@ contract Contributor is AragonApp {
|
|||||||
hashFunction = c.hashFunction;
|
hashFunction = c.hashFunction;
|
||||||
hashSize = c.hashSize;
|
hashSize = c.hashSize;
|
||||||
isCore = isCoreTeam(id);
|
isCore = isCoreTeam(id);
|
||||||
address token = getContract(uint8(Apps.Token));
|
balance = tokenContract.balanceOf(c.account);
|
||||||
balance = ITokenBalance(token).balanceOf(c.account);
|
totalKreditsEarned = contributionContract.totalKreditsEarnedByContributor(_id, true);
|
||||||
address contribution = getContract(uint8(Apps.Contribution));
|
contributionsCount = contributionContract.balanceOf(c.account);
|
||||||
totalKreditsEarned = IContributionBalance(contribution).totalKreditsEarnedByContributor(_id, true);
|
|
||||||
contributionsCount = IContributionBalance(contribution).balanceOf(c.account);
|
|
||||||
exists = c.exists;
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +1,17 @@
|
|||||||
pragma solidity ^0.4.24;
|
pragma solidity ^0.8.0;
|
||||||
|
|
||||||
import "@aragon/os/contracts/apps/AragonApp.sol";
|
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||||
import "@aragon/os/contracts/kernel/IKernel.sol";
|
|
||||||
|
|
||||||
contract Reimbursement is AragonApp {
|
interface ContributorInterface {
|
||||||
bytes32 public constant ADD_REIMBURSEMENT_ROLE = keccak256("ADD_REIMBURSEMENT_ROLE");
|
function getContributorAddressById(uint32 contributorId) external view returns (address);
|
||||||
bytes32 public constant VETO_REIMBURSEMENT_ROLE = keccak256("VETO_REIMBURSEMENT_ROLE");
|
function getContributorIdByAddress(address contributorAccount) external view returns (uint32);
|
||||||
// bytes32 public constant MANAGE_APPS_ROLE = keccak256("MANAGE_APPS_ROLE");
|
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 {
|
struct ReimbursementData {
|
||||||
uint32 recipientId;
|
uint32 recipientId;
|
||||||
@ -28,13 +33,27 @@ contract Reimbursement is AragonApp {
|
|||||||
event ReimbursementAdded(uint32 id, address indexed addedByAccount, uint256 amount);
|
event ReimbursementAdded(uint32 id, address indexed addedByAccount, uint256 amount);
|
||||||
event ReimbursementVetoed(uint32 id, address vetoedByAccount);
|
event ReimbursementVetoed(uint32 id, address vetoedByAccount);
|
||||||
|
|
||||||
function initialize() public onlyInit {
|
function initialize() public initializer {
|
||||||
blocksToWait = 40320; // 7 days; 15 seconds block time
|
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) {
|
function totalAmount(bool confirmedOnly) public view returns (uint256 amount) {
|
||||||
for (uint32 i = 1; i <= reimbursementsCount; i++) {
|
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;
|
uint32 reimbursementId = reimbursementsCount + 1;
|
||||||
ReimbursementData storage r = reimbursements[reimbursementId];
|
ReimbursementData storage r = reimbursements[reimbursementId];
|
||||||
r.exists = true;
|
r.exists = true;
|
||||||
@ -79,7 +98,7 @@ contract Reimbursement is AragonApp {
|
|||||||
emit ReimbursementAdded(reimbursementId, msg.sender, amount);
|
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];
|
ReimbursementData storage r = reimbursements[reimbursementId];
|
||||||
require(r.exists, 'NOT_FOUND');
|
require(r.exists, 'NOT_FOUND');
|
||||||
require(block.number < r.confirmedAtBlock, 'VETO_PERIOD_ENDED');
|
require(block.number < r.confirmedAtBlock, 'VETO_PERIOD_ENDED');
|
44
contracts/Token.sol
Normal file
44
contracts/Token.sol
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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)));
|
|
||||||
}
|
|
||||||
}
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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
89
hardhat.config.js
Normal 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
@ -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
8
lib/addresses.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"1337": {
|
||||||
|
"Contributor": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
|
||||||
|
"Contribution": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9",
|
||||||
|
"Token": "0x0165878A594ca255338adfa4d48449f69242Eb8F",
|
||||||
|
"Reimbursement": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
|
@ -14,15 +14,6 @@ class Base {
|
|||||||
return this.contract.address;
|
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) {
|
on (type, callback) {
|
||||||
return this.contract.on(type, callback);
|
return this.contract.on(type, callback);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,4 @@ module.exports = {
|
|||||||
Proposal: require('./proposal'),
|
Proposal: require('./proposal'),
|
||||||
Token: require('./token'),
|
Token: require('./token'),
|
||||||
Reimbursement: require('./reimbursement'),
|
Reimbursement: require('./reimbursement'),
|
||||||
Kernel: require('./kernel'),
|
|
||||||
Acl: require('./acl'),
|
|
||||||
};
|
};
|
||||||
|
@ -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;
|
|
@ -1,64 +1,66 @@
|
|||||||
const Record = require('./record');
|
const Record = require("./record");
|
||||||
const ExpenseSerializer = require('../serializers/expense');
|
const ExpenseSerializer = require("../serializers/expense");
|
||||||
|
|
||||||
class Reimbursement extends Record {
|
class Reimbursement extends Record {
|
||||||
get count () {
|
get count () {
|
||||||
return this.functions.reimbursementsCount();
|
return this.contract.reimbursementsCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
getById (id) {
|
getById (id) {
|
||||||
return this.functions.get(id)
|
return this.contract.get(id).then((data) => {
|
||||||
.then(data => {
|
return this.ipfs.catAndMerge(data, (ipfsDocument) => {
|
||||||
return this.ipfs.catAndMerge(data, (ipfsDocument) => {
|
const expenses = JSON.parse(ipfsDocument);
|
||||||
const expenses = JSON.parse(ipfsDocument);
|
return { expenses };
|
||||||
return { expenses };
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getData (id) {
|
getData (id) {
|
||||||
return this.functions.getReimbursement(id);
|
return this.contract.getReimbursement(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async add (attrs, callOptions = {}) {
|
async add (attrs, callOptions = {}) {
|
||||||
const amount = parseInt(attrs.amount);
|
const amount = parseInt(attrs.amount);
|
||||||
const token = attrs.token;
|
const token = attrs.token;
|
||||||
const recipientId = attrs.recipientId;
|
const recipientId = attrs.recipientId;
|
||||||
const expenses = attrs.expenses.map( e => new ExpenseSerializer(e) );
|
const expenses = attrs.expenses.map((e) => new ExpenseSerializer(e));
|
||||||
let errorMessage;
|
let errorMessage;
|
||||||
|
|
||||||
if (typeof amount !== 'number' || amount <= 0) {
|
if (typeof amount !== "number" || amount <= 0) {
|
||||||
errorMessage = 'Invalid data: amount must be a positive number.';
|
errorMessage = "Invalid data: amount must be a positive number.";
|
||||||
}
|
}
|
||||||
if (!token || token === '') {
|
if (!token || token === "") {
|
||||||
errorMessage = 'Invalid data: token must be a token address.';
|
errorMessage = "Invalid data: token must be a token address.";
|
||||||
}
|
}
|
||||||
if (!recipientId || recipientId === '') {
|
if (!recipientId || recipientId === "") {
|
||||||
errorMessage = 'Invalid data: recipientId is required.';
|
errorMessage = "Invalid data: recipientId is required.";
|
||||||
}
|
}
|
||||||
if (expenses.length === 0) {
|
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()))
|
return Promise.all(expenses.map((e) => e.validate())).then(() => {
|
||||||
.then(() => {
|
const jsonStr = JSON.stringify(
|
||||||
const jsonStr = JSON.stringify(expenses.map(e => e.data), null, 2);
|
expenses.map((e) => e.data),
|
||||||
return this.ipfs
|
null,
|
||||||
.add(jsonStr)
|
2
|
||||||
.then(ipfsHashAttr => {
|
);
|
||||||
const reimbursement = [
|
return this.ipfs.add(jsonStr).then((ipfsHashAttr) => {
|
||||||
amount,
|
const reimbursement = [
|
||||||
token,
|
amount,
|
||||||
parseInt(recipientId),
|
token,
|
||||||
ipfsHashAttr.hashDigest,
|
parseInt(recipientId),
|
||||||
ipfsHashAttr.hashFunction,
|
ipfsHashAttr.hashDigest,
|
||||||
ipfsHashAttr.hashSize,
|
ipfsHashAttr.hashFunction,
|
||||||
];
|
ipfsHashAttr.hashSize,
|
||||||
|
];
|
||||||
|
|
||||||
return this.functions.add(...reimbursement, callOptions);
|
return this.contract.add(...reimbursement, callOptions);
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,19 +8,14 @@ const ABIS = {
|
|||||||
Contribution: require('./abis/Contribution.json'),
|
Contribution: require('./abis/Contribution.json'),
|
||||||
Reimbursement: require('./abis/Reimbursement.json'),
|
Reimbursement: require('./abis/Reimbursement.json'),
|
||||||
Token: require('./abis/Token.json'),
|
Token: require('./abis/Token.json'),
|
||||||
Proposal: require('./abis/Proposal.json'),
|
|
||||||
Kernel: require('./abis/Kernel.json'),
|
|
||||||
Acl: require('./abis/ACL.json'),
|
|
||||||
};
|
};
|
||||||
const APP_CONTRACTS = [
|
// const APP_CONTRACTS = [
|
||||||
'Contributor',
|
// 'Contributor',
|
||||||
'Contribution',
|
// 'Contribution',
|
||||||
'Token',
|
// 'Token',
|
||||||
'Proposal',
|
// 'Reimbursement',
|
||||||
'Reimbursement',
|
// ];
|
||||||
'Acl',
|
const Addresses = require('./addresses.json');
|
||||||
];
|
|
||||||
const DaoAddresses = require('./addresses/dao.json');
|
|
||||||
|
|
||||||
const Contracts = require('./contracts');
|
const Contracts = require('./contracts');
|
||||||
const IPFS = require('./utils/ipfs');
|
const IPFS = require('./utils/ipfs');
|
||||||
@ -34,7 +29,7 @@ function capitalize (word) {
|
|||||||
class Kredits {
|
class Kredits {
|
||||||
|
|
||||||
constructor (provider, signer, options = {}) {
|
constructor (provider, signer, options = {}) {
|
||||||
let { addresses, abis, ipfsConfig } = options;
|
const { addresses, abis, ipfsConfig } = options;
|
||||||
|
|
||||||
this.provider = provider;
|
this.provider = provider;
|
||||||
this.signer = signer;
|
this.signer = signer;
|
||||||
@ -45,21 +40,15 @@ class Kredits {
|
|||||||
this.contracts = {};
|
this.contracts = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
init (names) {
|
init (/* names */) {
|
||||||
let contractsToLoad = names || APP_CONTRACTS;
|
// TODO implement
|
||||||
return this.provider.getNetwork().then(network => {
|
// const contractsToLoad = names || APP_CONTRACTS;
|
||||||
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}`
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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 () {
|
static availableNetworks () {
|
||||||
return Object.keys(DaoAddresses);
|
return Object.keys(Addresses);
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get Contributor () {
|
get Contributor () {
|
||||||
@ -104,11 +84,7 @@ class Kredits {
|
|||||||
|
|
||||||
get Contributors () {
|
get Contributors () {
|
||||||
deprecate('Contributors is deprecated use Contributor instead');
|
deprecate('Contributors is deprecated use Contributor instead');
|
||||||
return this.Contributor;
|
return this.contractFor('Contributor');
|
||||||
}
|
|
||||||
|
|
||||||
get Proposal () {
|
|
||||||
return this.contractFor('Proposal');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get Operator () {
|
get Operator () {
|
||||||
@ -127,10 +103,6 @@ class Kredits {
|
|||||||
return this.contractFor('Reimbursement');
|
return this.contractFor('Reimbursement');
|
||||||
}
|
}
|
||||||
|
|
||||||
get Acl () {
|
|
||||||
return this.contractFor('Acl');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should be private
|
// Should be private
|
||||||
contractFor (name) {
|
contractFor (name) {
|
||||||
if (this.contracts[name]) {
|
if (this.contracts[name]) {
|
||||||
@ -144,8 +116,8 @@ class Kredits {
|
|||||||
throw new Error(`Address or ABI not found for ${contractName}`);
|
throw new Error(`Address or ABI not found for ${contractName}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
let signerOrProvider = this.signer || this.provider;
|
const signerOrProvider = this.signer || this.provider;
|
||||||
let contract = new ethers.Contract(address, abi, signerOrProvider);
|
const contract = new ethers.Contract(address, abi, signerOrProvider);
|
||||||
this.contracts[name] = new Contracts[contractName](contract);
|
this.contracts[name] = new Contracts[contractName](contract);
|
||||||
this.contracts[name].ipfs = this.ipfs;
|
this.contracts[name].ipfs = this.ipfs;
|
||||||
|
|
||||||
|
@ -8,10 +8,10 @@ class IPFS {
|
|||||||
config = { host: 'localhost', port: '5001', protocol: 'http' };
|
config = { host: 'localhost', port: '5001', protocol: 'http' };
|
||||||
}
|
}
|
||||||
this._config = config;
|
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
|
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 no hash details are found simply return the data; nothing to merge
|
||||||
if (!data.hashSize || data.hashSize === 0) {
|
if (!data.hashSize || data.hashSize === 0) {
|
||||||
@ -22,20 +22,19 @@ class IPFS {
|
|||||||
|
|
||||||
return this.cat(data.ipfsHash)
|
return this.cat(data.ipfsHash)
|
||||||
.then(deserialize)
|
.then(deserialize)
|
||||||
.then((attributes) => {
|
.then(attributes => {
|
||||||
return Object.assign({}, data, attributes);
|
return Object.assign({}, data, attributes);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
add (data) {
|
async add (data) {
|
||||||
return this._ipfsAPI
|
return this._ipfsAPI.add(data)
|
||||||
.add(ipfsClient.Buffer.from(data))
|
.then(res => {
|
||||||
.then((res) => {
|
return this.decodeHash(res.path);
|
||||||
return this.decodeHash(res[0].hash);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cat (hashData) {
|
async cat (hashData) {
|
||||||
let ipfsHash = hashData; // default - if it is a string
|
let ipfsHash = hashData; // default - if it is a string
|
||||||
if (Object.prototype.hasOwnProperty.call(hashData, 'hashSize')) {
|
if (Object.prototype.hasOwnProperty.call(hashData, 'hashSize')) {
|
||||||
ipfsHash = this.encodeHash(hashData);
|
ipfsHash = this.encodeHash(hashData);
|
||||||
@ -43,7 +42,12 @@ class IPFS {
|
|||||||
if (this._config['gatewayUrl']) {
|
if (this._config['gatewayUrl']) {
|
||||||
return fetch(`${this._config['gatewayUrl']}/${ipfsHash}`).then(r => r.text());
|
return fetch(`${this._config['gatewayUrl']}/${ipfsHash}`).then(r => r.text());
|
||||||
} else {
|
} 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) {
|
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);
|
return multihashes.encode(digest, hashData.hashFunction, hashData.hashSize);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
var Migrations = artifacts.require("./Migrations.sol");
|
|
||||||
|
|
||||||
module.exports = function(deployer) {
|
|
||||||
deployer.deploy(Migrations);
|
|
||||||
};
|
|
35028
package-lock.json
generated
35028
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
57
package.json
57
package.json
@ -7,20 +7,17 @@
|
|||||||
"test": "test"
|
"test": "test"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"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",
|
"postshrinkwrap": "node scripts/fix-package-lock.js &>/dev/null || true",
|
||||||
"build-json": "npm run compile-contracts && node ./scripts/build-json.js",
|
"build": "npm run build:contracts && npm run build:json",
|
||||||
"repl": "truffle exec scripts/repl.js",
|
"build:contracts": "hardhat compile --force",
|
||||||
"seeds": "truffle exec scripts/seeds.js",
|
"build:json": "node ./scripts/build-json.js",
|
||||||
"compile-contracts": "truffle compile --all",
|
"seeds": "hardhat run scripts/seeds.js",
|
||||||
"bootstrap": "npm run reset:hard && npm run seeds",
|
"fund": "hardhat fund",
|
||||||
"reset": "npm run deploy:kit && npm run deploy:dao",
|
"bootstrap": "npm run build && npm run deploy:dao && npm run seeds",
|
||||||
"reset:hard": "npm run compile-contracts && npm run deploy:apps && npm run reset",
|
"repl": "hardhat console",
|
||||||
"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",
|
|
||||||
"lint:contracts": "solhint \"contracts/**/*.sol\" \"apps/*/contracts/**/*.sol\"",
|
"lint:contracts": "solhint \"contracts/**/*.sol\" \"apps/*/contracts/**/*.sol\"",
|
||||||
"lint:contract-tests": "eslint apps/*/test",
|
"lint:contract-tests": "eslint apps/*/test",
|
||||||
"lint:wrapper": "eslint lib/",
|
"lint:wrapper": "eslint lib/",
|
||||||
@ -33,37 +30,45 @@
|
|||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/67P/truffle-kredits.git"
|
"url": "git+https://github.com/67P/kredits-contracts.git"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"bugs": {
|
"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": {
|
"devDependencies": {
|
||||||
"@aragon/kits-base": "^1.0.0",
|
"@nomiclabs/hardhat-ethers": "^2.0.2",
|
||||||
"@aragon/os": "^4.4.0",
|
"@nomiclabs/hardhat-waffle": "^2.0.1",
|
||||||
|
"@openzeppelin/contracts-upgradeable": "^4.3.2",
|
||||||
|
"@openzeppelin/hardhat-upgrades": "^1.10.0",
|
||||||
"async-each-series": "^1.1.0",
|
"async-each-series": "^1.1.0",
|
||||||
"cli-table": "^0.3.1",
|
"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-import": "^2.20.2",
|
||||||
"eslint-plugin-node": "^11.1.0",
|
"eslint-plugin-node": "^11.1.0",
|
||||||
"eslint-plugin-promise": "^4.2.1",
|
"eslint-plugin-promise": "^6.0.0",
|
||||||
"eth-provider": "^0.2.5",
|
"eth-provider": "^0.11.0",
|
||||||
"ethereum-block-by-date": "^1.4.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",
|
"homedir": "^0.6.0",
|
||||||
"promptly": "^3.0.3",
|
"promptly": "^3.0.3",
|
||||||
"solc": "^0.6.8",
|
"solhint": "^3.3.7",
|
||||||
"solhint": "^2.3.1",
|
|
||||||
"truffle-hdwallet-provider": "^1.0.17",
|
"truffle-hdwallet-provider": "^1.0.17",
|
||||||
"truffle-hdwallet-provider-privkey": "^0.3.0",
|
"truffle-hdwallet-provider-privkey": "^0.3.0",
|
||||||
|
"web3-providers-ws": "^1.7.3",
|
||||||
"yargs": "^15.0.0"
|
"yargs": "^15.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ethers": "^5.0.2",
|
"@kosmos/schemas": "^3.1.0",
|
||||||
"ipfs-http-client": "^41.0.1",
|
"ethers": "^5.4.7",
|
||||||
"@kosmos/schemas": "^3.0.0",
|
"ipfs-http-client": "^56.0.3",
|
||||||
|
"multihashes": "^4.0.3",
|
||||||
"node-fetch": "^2.6.0",
|
"node-fetch": "^2.6.0",
|
||||||
"tv4": "^1.3.0"
|
"tv4": "^1.3.0"
|
||||||
},
|
},
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
const promptly = require('promptly');
|
const promptly = require('promptly');
|
||||||
const { inspect } = require('util');
|
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) {
|
async function main() {
|
||||||
let kredits;
|
kredits = new Kredits(hre.ethers.provider, hre.ethers.provider.getSigner())
|
||||||
try {
|
await kredits.init();
|
||||||
kredits = await initKredits(web3);
|
|
||||||
} catch(e) {
|
|
||||||
callback(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Using Contributions at: ${kredits.Contribution.contract.address}`);
|
console.log(`Using Contributions at: ${kredits.Contribution.contract.address}`);
|
||||||
|
|
||||||
@ -49,10 +45,11 @@ module.exports = async function(callback) {
|
|||||||
.then(result => {
|
.then(result => {
|
||||||
console.log("\n\nResult:");
|
console.log("\n\nResult:");
|
||||||
console.log(result);
|
console.log(result);
|
||||||
callback();
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.log('Failed to create contribution');
|
console.log('Failed to create contribution');
|
||||||
callback(inspect(error));
|
console.log(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
const promptly = require('promptly');
|
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) {
|
async function prompt(message, options) {
|
||||||
if (!options) {
|
if (!options) {
|
||||||
@ -8,15 +9,9 @@ async function prompt(message, options) {
|
|||||||
}
|
}
|
||||||
return await promptly.prompt(message, options);
|
return await promptly.prompt(message, options);
|
||||||
}
|
}
|
||||||
|
async function main() {
|
||||||
module.exports = async function(callback) {
|
kredits = new Kredits(hre.ethers.provider, hre.ethers.provider.getSigner())
|
||||||
let kredits;
|
await kredits.init();
|
||||||
try {
|
|
||||||
kredits = await initKredits(web3);
|
|
||||||
} catch(e) {
|
|
||||||
callback(e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Using contributors at: ${kredits.Contributor.contract.address}`);
|
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) => {
|
kredits.Contributor.add(contributorAttributes, { gasLimit: 350000 }).then((result) => {
|
||||||
console.log("\n\nResult:");
|
console.log("\n\nResult:");
|
||||||
console.log(result);
|
console.log(result);
|
||||||
callback();
|
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
console.log('Failed to create contributor');
|
console.log('Failed to create contributor');
|
||||||
callback(error);
|
console.log(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
@ -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));
|
|
||||||
});
|
|
||||||
}
|
|
@ -1,24 +1,18 @@
|
|||||||
const fs = require('fs');
|
const fs = require("fs");
|
||||||
const path = require('path');
|
const path = require("path");
|
||||||
|
|
||||||
const contractsPath = path.join(__dirname, '..', 'build', 'contracts');
|
const contractsPath = path.join(__dirname, "..", "artifacts", "contracts");
|
||||||
const libPath = path.join(__dirname, '..', 'lib');
|
const libPath = path.join(__dirname, "..", "lib");
|
||||||
const abisPath = path.join(libPath, 'abis');
|
const abisPath = path.join(libPath, "abis");
|
||||||
|
|
||||||
const files = [
|
const files = ["Contributor", "Contribution", "Token", "Reimbursement"];
|
||||||
'Contributor',
|
|
||||||
'Contribution',
|
|
||||||
'Kernel',
|
|
||||||
'Proposal',
|
|
||||||
'Token',
|
|
||||||
'Reimbursement',
|
|
||||||
'ACL'
|
|
||||||
];
|
|
||||||
|
|
||||||
files.forEach((fileName) => {
|
files.forEach((fileName) => {
|
||||||
let file = require(`${contractsPath}/${fileName}.json`);
|
let file = require(`${contractsPath}/${fileName}.sol/${fileName}.json`);
|
||||||
let abiFile = path.join(abisPath, `${fileName}.json`);
|
let abiFile = path.join(abisPath, `${fileName}.json`);
|
||||||
fs.writeFileSync(abiFile, JSON.stringify(file.abi));
|
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"
|
||||||
|
);
|
||||||
|
@ -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();
|
|
||||||
}
|
|
||||||
};
|
|
@ -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
78
scripts/create-proxy.js
Normal 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
Loading…
x
Reference in New Issue
Block a user