Compare commits
191 Commits
Author | SHA1 | Date | |
---|---|---|---|
0f872c8f1e | |||
484a9ffcaf | |||
6e9f565587 | |||
f4634fe692 | |||
68968e1fbd | |||
|
a2ebc609ea | ||
beca6afc4f | |||
|
90223dd22e | ||
ea3a591a0c | |||
|
e0f20d363c | ||
|
5e33381f2a | ||
1f91a13f06 | |||
7d2a492fc9 | |||
|
30490ce393 | ||
|
f0a71ca8f1 | ||
|
117918e66f | ||
d0d456b357 | |||
cda84fa2ce | |||
97e2db93be | |||
17c582f6df | |||
|
914d4d9585 | ||
|
0deeba14fc | ||
|
c61632d949 | ||
|
0b593ec795 | ||
|
401c528e95 | ||
ed2033980c | |||
17c7a36cb4 | |||
|
1cb777aa44 | ||
|
21522a86a0 | ||
|
269a2cf87a | ||
3112ef0e95 | |||
|
d63f7ca743 | ||
f10af14fae | |||
|
2692613b9a | ||
|
b49d1130a9 | ||
|
657cdc8afa | ||
|
ae9aa7aaaf | ||
|
708515ba4b | ||
7eceea5d57 | |||
|
089ffd42fe | ||
|
43d9bc0a07 | ||
|
6113e456af | ||
8ef6fc06ce | |||
|
093273f15b | ||
|
1dc54eccea | ||
|
bb662e377c | ||
|
d6bbc441f8 | ||
|
46090b3740 | ||
|
828f831c52 | ||
|
500180c6da | ||
|
b63c68cd1c | ||
c875e775b6 | |||
|
6e0ec8741e | ||
|
a1a68092f6 | ||
c6168e59e8 | |||
e810424163 | |||
f71ff4ce9a | |||
1c097f37a6 | |||
|
d3fb1010d5 | ||
f390b5dff5 | |||
|
1e4f7be5cf | ||
|
258c1cc755 | ||
|
f29054bc0b | ||
fd012d5359 | |||
|
5da710cc14 | ||
|
e99184b83f | ||
|
2b3fd1241d | ||
|
dc2c8130f3 | ||
|
796ccebd84 | ||
|
d72413eb66 | ||
|
9dd9d298cc | ||
|
67add71a22 | ||
|
cd07313679 | ||
|
90172071fa | ||
|
de1574155c | ||
|
0fc4eed09a | ||
|
55877897be | ||
|
59bda71f97 | ||
|
1521e272f9 | ||
|
990e2a9649 | ||
|
883f9adb96 | ||
|
550bc2b9f4 | ||
2fca436fa8 | |||
2b05be1897 | |||
|
46b1bbfbf2 | ||
|
98348dc544 | ||
|
fd93993a1b | ||
|
0d6702fd2b | ||
|
51e50e7c46 | ||
793642c238 | |||
|
c55593d46f | ||
|
2b314556ad | ||
53955126ff | |||
1d1f2bb4ed | |||
42a2945692 | |||
12326ce73f | |||
18da1306b9 | |||
f6c302dd21 | |||
e3a0f0e8d3 | |||
534f68c5a9 | |||
d0fd1a3971 | |||
a8597bba8e | |||
683ec5b2e9 | |||
d93d736370 | |||
bcee608920 | |||
643bd01a78 | |||
e21fb40661 | |||
fa4583158f | |||
d46b084e2c | |||
687040791c | |||
936115afb9 | |||
ba52e61d50 | |||
9d87636fb0 | |||
cc6b6de827 | |||
0d6dc78a58 | |||
195cd42313 | |||
1721c33620 | |||
96940716bd | |||
ab8f632a30 | |||
9e92fbfa8f | |||
75bcde4f49 | |||
53fafc1c78 | |||
16d5704173 | |||
23b30b7f8e | |||
50b10dd46c | |||
b80273b308 | |||
0fde17a4e0 | |||
ad8daa8649 | |||
d1d92c6644 | |||
53dbc1e4ad | |||
24990de42a | |||
d52f502fd5 | |||
04fff0934d | |||
22c3a82167 | |||
f0edfa038f | |||
8bb3f234c7 | |||
15a08fdaec | |||
0756569dc7 | |||
8a7858a0bb | |||
5e6d066e40 | |||
c3bd2b88d4 | |||
ffd4a5aeaf | |||
623cf6d6da | |||
cc9cc53d0b | |||
9bfe131cd5 | |||
8a962aea62 | |||
b81fc98740 | |||
5d4c9ba403 | |||
f5ca44d779 | |||
dd70bf77d5 | |||
63e8ce1e3f | |||
a626409221 | |||
1425c3664a | |||
c865c154a4 | |||
a63a37c5d6 | |||
20879c4c08 | |||
7ab8e4e52d | |||
f11c4f7764 | |||
44cad2d26e | |||
357698db02 | |||
6c80179a3d | |||
c471060cfd | |||
7418d33e63 | |||
1cfbc09e4a | |||
8984ba3802 | |||
2238ccf40e | |||
0df7930e06 | |||
eebc0db02b | |||
b22d16e61e | |||
6bc6bcb7f6 | |||
9df58b7f9a | |||
c83a560e3b | |||
eadca6904a | |||
89829ee81f | |||
32b212f9cf | |||
e3c03bf4a0 | |||
7dba0e4501 | |||
e6349f0594 | |||
15b47dbc42 | |||
7ad2515b67 | |||
c63fcc002b | |||
c4e7e1259e | |||
17cc44cb98 | |||
2c567fa71a | |||
9b4a95f375 | |||
687f87f43b | |||
7fdeb78617 | |||
19f212f283 | |||
1f248812a7 | |||
3f8407fa02 | |||
a0b0183beb |
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@ -0,0 +1,6 @@
|
||||
.openzeppelin
|
||||
artifacts
|
||||
cache
|
||||
deployments
|
||||
gitno
|
||||
node_modules
|
40
.drone.yml
Normal file
40
.drone.yml
Normal file
@ -0,0 +1,40 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: setup
|
||||
image: gitea.kosmos.org/kredits/docker-ci:latest
|
||||
commands:
|
||||
- cp -r /app/node_modules /drone/src/node_modules
|
||||
- chown -R drone:drone /drone/src
|
||||
- su drone -c 'npm install'
|
||||
- name: lint js
|
||||
image: gitea.kosmos.org/kredits/docker-ci:latest
|
||||
commands:
|
||||
- su drone -c 'npm run lint:wrapper'
|
||||
depends_on:
|
||||
- setup
|
||||
# - name: lint contracts
|
||||
# image: gitea.kosmos.org/kredits/docker-ci:latest
|
||||
# commands:
|
||||
# - su drone -c 'npm run lint:contracts'
|
||||
# depends_on:
|
||||
# - setup
|
||||
- name: build contracts
|
||||
image: gitea.kosmos.org/kredits/docker-ci:latest
|
||||
commands:
|
||||
- su drone -c 'npm run devchain -- --silent' &
|
||||
- sleep 5
|
||||
- su drone -c 'npm run build'
|
||||
depends_on:
|
||||
- setup
|
||||
- name: test
|
||||
image: gitea.kosmos.org/kredits/docker-ci:latest
|
||||
commands:
|
||||
- su drone -c 'npm run devchain -- --silent' &
|
||||
- sleep 5
|
||||
- su drone -c 'npm test'
|
||||
depends_on:
|
||||
- setup
|
||||
- build contracts
|
@ -1 +1,2 @@
|
||||
/scripts/
|
||||
/test/
|
||||
|
14
.gitea/release-drafter.yml
Normal file
14
.gitea/release-drafter.yml
Normal file
@ -0,0 +1,14 @@
|
||||
name-template: 'v$RESOLVED_VERSION'
|
||||
tag-template: 'v$RESOLVED_VERSION'
|
||||
version-resolver:
|
||||
major:
|
||||
labels:
|
||||
- release/major
|
||||
minor:
|
||||
labels:
|
||||
- release/minor
|
||||
- feature
|
||||
patch:
|
||||
labels:
|
||||
- release/patch
|
||||
default: patch
|
11
.gitea/workflows/release_drafter.yml
Normal file
11
.gitea/workflows/release_drafter.yml
Normal file
@ -0,0 +1,11 @@
|
||||
name: Release Drafter
|
||||
on:
|
||||
pull_request:
|
||||
types: [closed]
|
||||
jobs:
|
||||
release_drafter_job:
|
||||
name: Update release notes draft
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Release Drafter
|
||||
uses: https://github.com/raucao/gitea-release-drafter@dev
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,8 +1,13 @@
|
||||
build
|
||||
flattened_contracts
|
||||
node_modules
|
||||
**/node_modules
|
||||
.ganache-db
|
||||
.tm_properties
|
||||
yarn-error.log
|
||||
.DS_Store
|
||||
|
||||
data
|
||||
|
||||
cache
|
||||
artifacts
|
||||
.openzeppelin/unknown-1337.json
|
||||
.env
|
||||
|
0
.openzeppelin/.keep
Normal file
0
.openzeppelin/.keep
Normal file
937
.openzeppelin/unknown-31.json
Normal file
937
.openzeppelin/unknown-31.json
Normal file
@ -0,0 +1,937 @@
|
||||
{
|
||||
"manifestVersion": "3.2",
|
||||
"admin": {
|
||||
"address": "0xcf844786B3E8b23f6d6D8CA845c0DE07047D54a7",
|
||||
"txHash": "0xafcf40769c999bae279f48e7d81d0f698c82286dd4178029021f82ecb549e645"
|
||||
},
|
||||
"proxies": [
|
||||
{
|
||||
"address": "0x0E4b2A1b7655266Ba9A029B50eEB42DBCd83090B",
|
||||
"txHash": "0xfd629e11999d0448404c81e33ea79cbd18f8a978cf93afcf5c45e97eb63991f8",
|
||||
"kind": "transparent"
|
||||
},
|
||||
{
|
||||
"address": "0x242fab10d4F395B9DCC59b8Adc6629722c2E65d2",
|
||||
"txHash": "0xcd6633873db24736e9f40a4c401b658b469b290176e0d03e640ccfb7945a5426",
|
||||
"kind": "transparent"
|
||||
},
|
||||
{
|
||||
"address": "0x4fe4311a91F90ff98f0978002D84AE4771fe7cF1",
|
||||
"txHash": "0x8b6e5088ad7f39918bf9e3e67cdc45bd9bf2a62b13e4f7a1441ad3bca27b4c04",
|
||||
"kind": "transparent"
|
||||
},
|
||||
{
|
||||
"address": "0x252b3Ab7913d6Ed8f303a7e5452F0BFf8bE6b64d",
|
||||
"txHash": "0x138a863f21ab0ef2400434280bc8c9e389e6dead125b5622deb9533eb2506a53",
|
||||
"kind": "transparent"
|
||||
},
|
||||
{
|
||||
"address": "0x2C5cf87C0dE8e4E3642de900Fa7B4b630163E2aA",
|
||||
"txHash": "0x0e4310cd5faba8ce7cae60b3de1412050b4315957e58bc8415680eb5e9de0314",
|
||||
"kind": "transparent"
|
||||
},
|
||||
{
|
||||
"address": "0x95DC31665D193E377f54b70C535fcDb205525291",
|
||||
"txHash": "0xb03a4d57e80f73d65f29f75fc419fd6197148e979ed9c330353c8574d952c544",
|
||||
"kind": "transparent"
|
||||
},
|
||||
{
|
||||
"address": "0x049bA8E70FEbFfd6d03C71211bDA37B4ff064115",
|
||||
"txHash": "0x5f561059950c1e06fc62cfc7cac4c4618a0e066704fb680b659ae9a08b6e1d27",
|
||||
"kind": "transparent"
|
||||
},
|
||||
{
|
||||
"address": "0x7ab26A0f00eF0D6e05e5BDE047505a4eD53aF809",
|
||||
"txHash": "0xfa73dbe843ea1151f925f531365fb919b6138c8963e62332c8e00cf5141e457c",
|
||||
"kind": "transparent"
|
||||
},
|
||||
{
|
||||
"address": "0x99EC72b34295b62f4bC1527Da461262c615a0b2c",
|
||||
"txHash": "0x1ca664ffdf50a6519b900c84e715d43a59ae39b35d5914ecb964f5440d486098",
|
||||
"kind": "transparent"
|
||||
}
|
||||
],
|
||||
"impls": {
|
||||
"4ef0a0d41e92c95e53ad65fb957441f9b9443f4fe2d1c2221dfde7d1341db071": {
|
||||
"address": "0x73069A23A5252E9c0D0b5F0493918277074ED987",
|
||||
"txHash": "0x4c139a7a70dea23d9fdbcb7d8d381be7593fc3d80d59f29aec0fd6f16698136b",
|
||||
"layout": {
|
||||
"storage": [
|
||||
{
|
||||
"label": "_initialized",
|
||||
"offset": 0,
|
||||
"slot": "0",
|
||||
"type": "t_uint8",
|
||||
"contract": "Initializable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62",
|
||||
"retypedFrom": "bool"
|
||||
},
|
||||
{
|
||||
"label": "_initializing",
|
||||
"offset": 1,
|
||||
"slot": "0",
|
||||
"type": "t_bool",
|
||||
"contract": "Initializable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67"
|
||||
},
|
||||
{
|
||||
"label": "deployer",
|
||||
"offset": 2,
|
||||
"slot": "0",
|
||||
"type": "t_address",
|
||||
"contract": "Contributor",
|
||||
"src": "contracts/Contributor.sol:15"
|
||||
},
|
||||
{
|
||||
"label": "contributionContract",
|
||||
"offset": 0,
|
||||
"slot": "1",
|
||||
"type": "t_contract(IContributionBalance)2238",
|
||||
"contract": "Contributor",
|
||||
"src": "contracts/Contributor.sol:16"
|
||||
},
|
||||
{
|
||||
"label": "tokenContract",
|
||||
"offset": 0,
|
||||
"slot": "2",
|
||||
"type": "t_contract(IToken)2221",
|
||||
"contract": "Contributor",
|
||||
"src": "contracts/Contributor.sol:17"
|
||||
},
|
||||
{
|
||||
"label": "contributorIds",
|
||||
"offset": 0,
|
||||
"slot": "3",
|
||||
"type": "t_mapping(t_address,t_uint32)",
|
||||
"contract": "Contributor",
|
||||
"src": "contracts/Contributor.sol:28"
|
||||
},
|
||||
{
|
||||
"label": "contributors",
|
||||
"offset": 0,
|
||||
"slot": "4",
|
||||
"type": "t_mapping(t_uint32,t_struct(Contributor)2261_storage)",
|
||||
"contract": "Contributor",
|
||||
"src": "contracts/Contributor.sol:29"
|
||||
},
|
||||
{
|
||||
"label": "contributorsCount",
|
||||
"offset": 0,
|
||||
"slot": "5",
|
||||
"type": "t_uint32",
|
||||
"contract": "Contributor",
|
||||
"src": "contracts/Contributor.sol:30"
|
||||
},
|
||||
{
|
||||
"label": "profileManager",
|
||||
"offset": 4,
|
||||
"slot": "5",
|
||||
"type": "t_address",
|
||||
"contract": "Contributor",
|
||||
"src": "contracts/Contributor.sol:32"
|
||||
}
|
||||
],
|
||||
"types": {
|
||||
"t_address": {
|
||||
"label": "address",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_bool": {
|
||||
"label": "bool",
|
||||
"numberOfBytes": "1"
|
||||
},
|
||||
"t_bytes32": {
|
||||
"label": "bytes32",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_contract(IContributionBalance)2238": {
|
||||
"label": "contract IContributionBalance",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_contract(IToken)2221": {
|
||||
"label": "contract IToken",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_mapping(t_address,t_uint32)": {
|
||||
"label": "mapping(address => uint32)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_uint32,t_struct(Contributor)2261_storage)": {
|
||||
"label": "mapping(uint32 => struct Contributor.Contributor)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_struct(Contributor)2261_storage": {
|
||||
"label": "struct Contributor.Contributor",
|
||||
"members": [
|
||||
{
|
||||
"label": "account",
|
||||
"type": "t_address",
|
||||
"offset": 0,
|
||||
"slot": "0"
|
||||
},
|
||||
{
|
||||
"label": "hashDigest",
|
||||
"type": "t_bytes32",
|
||||
"offset": 0,
|
||||
"slot": "1"
|
||||
},
|
||||
{
|
||||
"label": "hashFunction",
|
||||
"type": "t_uint8",
|
||||
"offset": 0,
|
||||
"slot": "2"
|
||||
},
|
||||
{
|
||||
"label": "hashSize",
|
||||
"type": "t_uint8",
|
||||
"offset": 1,
|
||||
"slot": "2"
|
||||
},
|
||||
{
|
||||
"label": "exists",
|
||||
"type": "t_bool",
|
||||
"offset": 2,
|
||||
"slot": "2"
|
||||
},
|
||||
{
|
||||
"label": "kreditsWithdrawn",
|
||||
"type": "t_uint32",
|
||||
"offset": 3,
|
||||
"slot": "2"
|
||||
}
|
||||
],
|
||||
"numberOfBytes": "96"
|
||||
},
|
||||
"t_uint32": {
|
||||
"label": "uint32",
|
||||
"numberOfBytes": "4"
|
||||
},
|
||||
"t_uint8": {
|
||||
"label": "uint8",
|
||||
"numberOfBytes": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"960de0fa0d50c2ede5b2a868a9475e44622a753d4ff7829422c7eb342572b189": {
|
||||
"address": "0x872fa1C9d207b5ce3CE786f2099c3EeA621dFD70",
|
||||
"txHash": "0xb338b0878d66e466425c9032ef0f9019f91cda395fae4f83c999534249cd228d",
|
||||
"layout": {
|
||||
"storage": [
|
||||
{
|
||||
"label": "_initialized",
|
||||
"offset": 0,
|
||||
"slot": "0",
|
||||
"type": "t_uint8",
|
||||
"contract": "Initializable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62",
|
||||
"retypedFrom": "bool"
|
||||
},
|
||||
{
|
||||
"label": "_initializing",
|
||||
"offset": 1,
|
||||
"slot": "0",
|
||||
"type": "t_bool",
|
||||
"contract": "Initializable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67"
|
||||
},
|
||||
{
|
||||
"label": "contributorContract",
|
||||
"offset": 2,
|
||||
"slot": "0",
|
||||
"type": "t_contract(ContributorInterface)1546",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:14"
|
||||
},
|
||||
{
|
||||
"label": "name_",
|
||||
"offset": 0,
|
||||
"slot": "1",
|
||||
"type": "t_string_storage",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:28"
|
||||
},
|
||||
{
|
||||
"label": "symbol_",
|
||||
"offset": 0,
|
||||
"slot": "2",
|
||||
"type": "t_string_storage",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:29"
|
||||
},
|
||||
{
|
||||
"label": "contributionOwner",
|
||||
"offset": 0,
|
||||
"slot": "3",
|
||||
"type": "t_mapping(t_uint32,t_uint32)",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:32"
|
||||
},
|
||||
{
|
||||
"label": "ownedContributions",
|
||||
"offset": 0,
|
||||
"slot": "4",
|
||||
"type": "t_mapping(t_uint32,t_array(t_uint32)dyn_storage)",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:34"
|
||||
},
|
||||
{
|
||||
"label": "contributions",
|
||||
"offset": 0,
|
||||
"slot": "5",
|
||||
"type": "t_mapping(t_uint32,t_struct(ContributionData)1570_storage)",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:36"
|
||||
},
|
||||
{
|
||||
"label": "contributionsCount",
|
||||
"offset": 0,
|
||||
"slot": "6",
|
||||
"type": "t_uint32",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:37"
|
||||
},
|
||||
{
|
||||
"label": "blocksToWait",
|
||||
"offset": 4,
|
||||
"slot": "6",
|
||||
"type": "t_uint32",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:40"
|
||||
},
|
||||
{
|
||||
"label": "deployer",
|
||||
"offset": 8,
|
||||
"slot": "6",
|
||||
"type": "t_address",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:43"
|
||||
},
|
||||
{
|
||||
"label": "migrationDone",
|
||||
"offset": 28,
|
||||
"slot": "6",
|
||||
"type": "t_bool",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:46"
|
||||
}
|
||||
],
|
||||
"types": {
|
||||
"t_address": {
|
||||
"label": "address",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_array(t_uint32)dyn_storage": {
|
||||
"label": "uint32[]",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_bool": {
|
||||
"label": "bool",
|
||||
"numberOfBytes": "1"
|
||||
},
|
||||
"t_bytes32": {
|
||||
"label": "bytes32",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_contract(ContributorInterface)1546": {
|
||||
"label": "contract ContributorInterface",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_mapping(t_uint32,t_array(t_uint32)dyn_storage)": {
|
||||
"label": "mapping(uint32 => uint32[])",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_uint32,t_struct(ContributionData)1570_storage)": {
|
||||
"label": "mapping(uint32 => struct Contribution.ContributionData)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_uint32,t_uint32)": {
|
||||
"label": "mapping(uint32 => uint32)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_string_storage": {
|
||||
"label": "string",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_struct(ContributionData)1570_storage": {
|
||||
"label": "struct Contribution.ContributionData",
|
||||
"members": [
|
||||
{
|
||||
"label": "contributorId",
|
||||
"type": "t_uint32",
|
||||
"offset": 0,
|
||||
"slot": "0"
|
||||
},
|
||||
{
|
||||
"label": "amount",
|
||||
"type": "t_uint32",
|
||||
"offset": 4,
|
||||
"slot": "0"
|
||||
},
|
||||
{
|
||||
"label": "hashDigest",
|
||||
"type": "t_bytes32",
|
||||
"offset": 0,
|
||||
"slot": "1"
|
||||
},
|
||||
{
|
||||
"label": "hashFunction",
|
||||
"type": "t_uint8",
|
||||
"offset": 0,
|
||||
"slot": "2"
|
||||
},
|
||||
{
|
||||
"label": "hashSize",
|
||||
"type": "t_uint8",
|
||||
"offset": 1,
|
||||
"slot": "2"
|
||||
},
|
||||
{
|
||||
"label": "tokenMetadataURL",
|
||||
"type": "t_string_storage",
|
||||
"offset": 0,
|
||||
"slot": "3"
|
||||
},
|
||||
{
|
||||
"label": "confirmedAtBlock",
|
||||
"type": "t_uint256",
|
||||
"offset": 0,
|
||||
"slot": "4"
|
||||
},
|
||||
{
|
||||
"label": "vetoed",
|
||||
"type": "t_bool",
|
||||
"offset": 0,
|
||||
"slot": "5"
|
||||
},
|
||||
{
|
||||
"label": "exists",
|
||||
"type": "t_bool",
|
||||
"offset": 1,
|
||||
"slot": "5"
|
||||
}
|
||||
],
|
||||
"numberOfBytes": "192"
|
||||
},
|
||||
"t_uint256": {
|
||||
"label": "uint256",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_uint32": {
|
||||
"label": "uint32",
|
||||
"numberOfBytes": "4"
|
||||
},
|
||||
"t_uint8": {
|
||||
"label": "uint8",
|
||||
"numberOfBytes": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"75d216a88a04f139c0c69c1369ddd6ded974ebf995ccce44546b6ace2dabb597": {
|
||||
"address": "0x2D7848AF9Af0d2d8D67bbaAbf84D58Bd19F17eFa",
|
||||
"txHash": "0x8a6621bfc3db4c2bc2d294d69e6d25d499c7667b8da1910b3b0f2bc43968adba",
|
||||
"layout": {
|
||||
"storage": [
|
||||
{
|
||||
"label": "_initialized",
|
||||
"offset": 0,
|
||||
"slot": "0",
|
||||
"type": "t_uint8",
|
||||
"contract": "Initializable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62",
|
||||
"retypedFrom": "bool"
|
||||
},
|
||||
{
|
||||
"label": "_initializing",
|
||||
"offset": 1,
|
||||
"slot": "0",
|
||||
"type": "t_bool",
|
||||
"contract": "Initializable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67"
|
||||
},
|
||||
{
|
||||
"label": "__gap",
|
||||
"offset": 0,
|
||||
"slot": "1",
|
||||
"type": "t_array(t_uint256)50_storage",
|
||||
"contract": "ContextUpgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol:36"
|
||||
},
|
||||
{
|
||||
"label": "_balances",
|
||||
"offset": 0,
|
||||
"slot": "51",
|
||||
"type": "t_mapping(t_address,t_uint256)",
|
||||
"contract": "ERC20Upgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:37"
|
||||
},
|
||||
{
|
||||
"label": "_allowances",
|
||||
"offset": 0,
|
||||
"slot": "52",
|
||||
"type": "t_mapping(t_address,t_mapping(t_address,t_uint256))",
|
||||
"contract": "ERC20Upgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:39"
|
||||
},
|
||||
{
|
||||
"label": "_totalSupply",
|
||||
"offset": 0,
|
||||
"slot": "53",
|
||||
"type": "t_uint256",
|
||||
"contract": "ERC20Upgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:41"
|
||||
},
|
||||
{
|
||||
"label": "_name",
|
||||
"offset": 0,
|
||||
"slot": "54",
|
||||
"type": "t_string_storage",
|
||||
"contract": "ERC20Upgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:43"
|
||||
},
|
||||
{
|
||||
"label": "_symbol",
|
||||
"offset": 0,
|
||||
"slot": "55",
|
||||
"type": "t_string_storage",
|
||||
"contract": "ERC20Upgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:44"
|
||||
},
|
||||
{
|
||||
"label": "__gap",
|
||||
"offset": 0,
|
||||
"slot": "56",
|
||||
"type": "t_array(t_uint256)45_storage",
|
||||
"contract": "ERC20Upgradeable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol:400"
|
||||
},
|
||||
{
|
||||
"label": "contributorContract",
|
||||
"offset": 0,
|
||||
"slot": "101",
|
||||
"type": "t_contract(ContributorInterface)3364",
|
||||
"contract": "Token",
|
||||
"src": "contracts/Token.sol:13"
|
||||
},
|
||||
{
|
||||
"label": "contributorContractAddress",
|
||||
"offset": 0,
|
||||
"slot": "102",
|
||||
"type": "t_address",
|
||||
"contract": "Token",
|
||||
"src": "contracts/Token.sol:16"
|
||||
}
|
||||
],
|
||||
"types": {
|
||||
"t_address": {
|
||||
"label": "address",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_array(t_uint256)45_storage": {
|
||||
"label": "uint256[45]",
|
||||
"numberOfBytes": "1440"
|
||||
},
|
||||
"t_array(t_uint256)50_storage": {
|
||||
"label": "uint256[50]",
|
||||
"numberOfBytes": "1600"
|
||||
},
|
||||
"t_bool": {
|
||||
"label": "bool",
|
||||
"numberOfBytes": "1"
|
||||
},
|
||||
"t_contract(ContributorInterface)3364": {
|
||||
"label": "contract ContributorInterface",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_mapping(t_address,t_mapping(t_address,t_uint256))": {
|
||||
"label": "mapping(address => mapping(address => uint256))",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_address,t_uint256)": {
|
||||
"label": "mapping(address => uint256)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_string_storage": {
|
||||
"label": "string",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_uint256": {
|
||||
"label": "uint256",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_uint8": {
|
||||
"label": "uint8",
|
||||
"numberOfBytes": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"d06fbc00ef593e162a054b1f961d39eaf32e0734f8ba230d3519837b8548e33b": {
|
||||
"address": "0x5b8AF16247593465cC82b68A7Af49338E141A207",
|
||||
"txHash": "0x2e052dd42231aa43a484c5a4c376e73b70fd14026611be0b585c826d37fe829c",
|
||||
"layout": {
|
||||
"storage": [
|
||||
{
|
||||
"label": "_initialized",
|
||||
"offset": 0,
|
||||
"slot": "0",
|
||||
"type": "t_uint8",
|
||||
"contract": "Initializable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62",
|
||||
"retypedFrom": "bool"
|
||||
},
|
||||
{
|
||||
"label": "_initializing",
|
||||
"offset": 1,
|
||||
"slot": "0",
|
||||
"type": "t_bool",
|
||||
"contract": "Initializable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67"
|
||||
},
|
||||
{
|
||||
"label": "contributorContract",
|
||||
"offset": 2,
|
||||
"slot": "0",
|
||||
"type": "t_contract(ContributorInterface)2958",
|
||||
"contract": "Reimbursement",
|
||||
"src": "contracts/Reimbursement.sol:14"
|
||||
},
|
||||
{
|
||||
"label": "reimbursements",
|
||||
"offset": 0,
|
||||
"slot": "1",
|
||||
"type": "t_mapping(t_uint32,t_struct(ReimbursementData)2982_storage)",
|
||||
"contract": "Reimbursement",
|
||||
"src": "contracts/Reimbursement.sol:29"
|
||||
},
|
||||
{
|
||||
"label": "reimbursementsCount",
|
||||
"offset": 0,
|
||||
"slot": "2",
|
||||
"type": "t_uint32",
|
||||
"contract": "Reimbursement",
|
||||
"src": "contracts/Reimbursement.sol:30"
|
||||
},
|
||||
{
|
||||
"label": "blocksToWait",
|
||||
"offset": 4,
|
||||
"slot": "2",
|
||||
"type": "t_uint32",
|
||||
"contract": "Reimbursement",
|
||||
"src": "contracts/Reimbursement.sol:32"
|
||||
}
|
||||
],
|
||||
"types": {
|
||||
"t_address": {
|
||||
"label": "address",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_bool": {
|
||||
"label": "bool",
|
||||
"numberOfBytes": "1"
|
||||
},
|
||||
"t_bytes32": {
|
||||
"label": "bytes32",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_contract(ContributorInterface)2958": {
|
||||
"label": "contract ContributorInterface",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_mapping(t_uint32,t_struct(ReimbursementData)2982_storage)": {
|
||||
"label": "mapping(uint32 => struct Reimbursement.ReimbursementData)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_struct(ReimbursementData)2982_storage": {
|
||||
"label": "struct Reimbursement.ReimbursementData",
|
||||
"members": [
|
||||
{
|
||||
"label": "recipientId",
|
||||
"type": "t_uint32",
|
||||
"offset": 0,
|
||||
"slot": "0"
|
||||
},
|
||||
{
|
||||
"label": "amount",
|
||||
"type": "t_uint256",
|
||||
"offset": 0,
|
||||
"slot": "1"
|
||||
},
|
||||
{
|
||||
"label": "token",
|
||||
"type": "t_address",
|
||||
"offset": 0,
|
||||
"slot": "2"
|
||||
},
|
||||
{
|
||||
"label": "hashDigest",
|
||||
"type": "t_bytes32",
|
||||
"offset": 0,
|
||||
"slot": "3"
|
||||
},
|
||||
{
|
||||
"label": "hashFunction",
|
||||
"type": "t_uint8",
|
||||
"offset": 0,
|
||||
"slot": "4"
|
||||
},
|
||||
{
|
||||
"label": "hashSize",
|
||||
"type": "t_uint8",
|
||||
"offset": 1,
|
||||
"slot": "4"
|
||||
},
|
||||
{
|
||||
"label": "confirmedAtBlock",
|
||||
"type": "t_uint256",
|
||||
"offset": 0,
|
||||
"slot": "5"
|
||||
},
|
||||
{
|
||||
"label": "vetoed",
|
||||
"type": "t_bool",
|
||||
"offset": 0,
|
||||
"slot": "6"
|
||||
},
|
||||
{
|
||||
"label": "exists",
|
||||
"type": "t_bool",
|
||||
"offset": 1,
|
||||
"slot": "6"
|
||||
}
|
||||
],
|
||||
"numberOfBytes": "224"
|
||||
},
|
||||
"t_uint256": {
|
||||
"label": "uint256",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_uint32": {
|
||||
"label": "uint32",
|
||||
"numberOfBytes": "4"
|
||||
},
|
||||
"t_uint8": {
|
||||
"label": "uint8",
|
||||
"numberOfBytes": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"27e6fdeedf4e7e81a2c2ecc839d0c13414aa8f5527d4d3573f469358c8aad652": {
|
||||
"address": "0xfaeA425E6eDd4e9B31dAa3614c7c7862751D6782",
|
||||
"txHash": "0xe7768586dbfb5d528db3c9d0c2a608a4fcb5d7f04eb9f89913ac21b8d20a72df",
|
||||
"layout": {
|
||||
"storage": [
|
||||
{
|
||||
"label": "_initialized",
|
||||
"offset": 0,
|
||||
"slot": "0",
|
||||
"type": "t_uint8",
|
||||
"contract": "Initializable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:62",
|
||||
"retypedFrom": "bool"
|
||||
},
|
||||
{
|
||||
"label": "_initializing",
|
||||
"offset": 1,
|
||||
"slot": "0",
|
||||
"type": "t_bool",
|
||||
"contract": "Initializable",
|
||||
"src": "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol:67"
|
||||
},
|
||||
{
|
||||
"label": "contributorContract",
|
||||
"offset": 2,
|
||||
"slot": "0",
|
||||
"type": "t_contract(ContributorInterface)1546",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:14"
|
||||
},
|
||||
{
|
||||
"label": "name_",
|
||||
"offset": 0,
|
||||
"slot": "1",
|
||||
"type": "t_string_storage",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:28"
|
||||
},
|
||||
{
|
||||
"label": "symbol_",
|
||||
"offset": 0,
|
||||
"slot": "2",
|
||||
"type": "t_string_storage",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:29"
|
||||
},
|
||||
{
|
||||
"label": "contributionOwner",
|
||||
"offset": 0,
|
||||
"slot": "3",
|
||||
"type": "t_mapping(t_uint32,t_uint32)",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:32"
|
||||
},
|
||||
{
|
||||
"label": "ownedContributions",
|
||||
"offset": 0,
|
||||
"slot": "4",
|
||||
"type": "t_mapping(t_uint32,t_array(t_uint32)dyn_storage)",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:34"
|
||||
},
|
||||
{
|
||||
"label": "contributions",
|
||||
"offset": 0,
|
||||
"slot": "5",
|
||||
"type": "t_mapping(t_uint32,t_struct(ContributionData)1570_storage)",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:36"
|
||||
},
|
||||
{
|
||||
"label": "contributionsCount",
|
||||
"offset": 0,
|
||||
"slot": "6",
|
||||
"type": "t_uint32",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:37"
|
||||
},
|
||||
{
|
||||
"label": "blocksToWait",
|
||||
"offset": 4,
|
||||
"slot": "6",
|
||||
"type": "t_uint32",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:40"
|
||||
},
|
||||
{
|
||||
"label": "deployer",
|
||||
"offset": 8,
|
||||
"slot": "6",
|
||||
"type": "t_address",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:43"
|
||||
},
|
||||
{
|
||||
"label": "migrationDone",
|
||||
"offset": 28,
|
||||
"slot": "6",
|
||||
"type": "t_bool",
|
||||
"contract": "Contribution",
|
||||
"src": "contracts/Contribution.sol:46"
|
||||
}
|
||||
],
|
||||
"types": {
|
||||
"t_address": {
|
||||
"label": "address",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_array(t_uint32)dyn_storage": {
|
||||
"label": "uint32[]",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_bool": {
|
||||
"label": "bool",
|
||||
"numberOfBytes": "1"
|
||||
},
|
||||
"t_bytes32": {
|
||||
"label": "bytes32",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_contract(ContributorInterface)1546": {
|
||||
"label": "contract ContributorInterface",
|
||||
"numberOfBytes": "20"
|
||||
},
|
||||
"t_mapping(t_uint32,t_array(t_uint32)dyn_storage)": {
|
||||
"label": "mapping(uint32 => uint32[])",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_uint32,t_struct(ContributionData)1570_storage)": {
|
||||
"label": "mapping(uint32 => struct Contribution.ContributionData)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_mapping(t_uint32,t_uint32)": {
|
||||
"label": "mapping(uint32 => uint32)",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_string_storage": {
|
||||
"label": "string",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_struct(ContributionData)1570_storage": {
|
||||
"label": "struct Contribution.ContributionData",
|
||||
"members": [
|
||||
{
|
||||
"label": "contributorId",
|
||||
"type": "t_uint32",
|
||||
"offset": 0,
|
||||
"slot": "0"
|
||||
},
|
||||
{
|
||||
"label": "amount",
|
||||
"type": "t_uint32",
|
||||
"offset": 4,
|
||||
"slot": "0"
|
||||
},
|
||||
{
|
||||
"label": "hashDigest",
|
||||
"type": "t_bytes32",
|
||||
"offset": 0,
|
||||
"slot": "1"
|
||||
},
|
||||
{
|
||||
"label": "hashFunction",
|
||||
"type": "t_uint8",
|
||||
"offset": 0,
|
||||
"slot": "2"
|
||||
},
|
||||
{
|
||||
"label": "hashSize",
|
||||
"type": "t_uint8",
|
||||
"offset": 1,
|
||||
"slot": "2"
|
||||
},
|
||||
{
|
||||
"label": "tokenMetadataURL",
|
||||
"type": "t_string_storage",
|
||||
"offset": 0,
|
||||
"slot": "3"
|
||||
},
|
||||
{
|
||||
"label": "confirmedAtBlock",
|
||||
"type": "t_uint256",
|
||||
"offset": 0,
|
||||
"slot": "4"
|
||||
},
|
||||
{
|
||||
"label": "vetoed",
|
||||
"type": "t_bool",
|
||||
"offset": 0,
|
||||
"slot": "5"
|
||||
},
|
||||
{
|
||||
"label": "exists",
|
||||
"type": "t_bool",
|
||||
"offset": 1,
|
||||
"slot": "5"
|
||||
}
|
||||
],
|
||||
"numberOfBytes": "192"
|
||||
},
|
||||
"t_uint256": {
|
||||
"label": "uint256",
|
||||
"numberOfBytes": "32"
|
||||
},
|
||||
"t_uint32": {
|
||||
"label": "uint32",
|
||||
"numberOfBytes": "4"
|
||||
},
|
||||
"t_uint8": {
|
||||
"label": "uint8",
|
||||
"numberOfBytes": "1"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,6 @@
|
||||
"solhint:recommended"
|
||||
],
|
||||
"rules": {
|
||||
"indent": "2"
|
||||
"max-line-length": "warn"
|
||||
}
|
||||
}
|
||||
|
38
.travis.yml
38
.travis.yml
@ -1,38 +0,0 @@
|
||||
---
|
||||
language: node_js
|
||||
node_js:
|
||||
- "lts/*"
|
||||
|
||||
sudo: false
|
||||
dist: xenial
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- apps/contribution/node_modules
|
||||
- apps/contributor/node_modules
|
||||
- apps/proposal/node_modules
|
||||
- apps/token/node_modules
|
||||
- apps/vault/node_modules
|
||||
|
||||
install:
|
||||
- npm install -g @aragon/cli
|
||||
- npm install -g truffle
|
||||
- npm install
|
||||
|
||||
before_script:
|
||||
- npm run devchain &
|
||||
- sleep 10
|
||||
|
||||
script:
|
||||
- npm run lint:wrapper
|
||||
- npm run lint:contract-tests
|
||||
# FIXME Fix tests
|
||||
# - npm run test:token
|
||||
# - npm run test:contributor
|
||||
# - npm run test:contribution
|
||||
# - npm run test:proposal
|
||||
|
||||
branches:
|
||||
only:
|
||||
- master
|
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
# syntax=docker/dockerfile:1
|
||||
|
||||
FROM node:16.16
|
||||
#ENV NODE_ENV=production
|
||||
|
||||
RUN useradd -ms /bin/bash drone
|
||||
|
||||
WORKDIR /app
|
||||
COPY ["package.json", "package-lock.json*", "./"]
|
||||
RUN chown -R drone:drone /app
|
||||
USER drone
|
||||
RUN npm install
|
||||
USER root
|
247
README.md
247
README.md
@ -1,14 +1,10 @@
|
||||
[](https://www.npmjs.com/package/kredits-contracts)
|
||||
[](https://www.npmjs.com/package/@kredits/contracts)
|
||||
[](https://drone.kosmos.org/kredits/contracts)
|
||||
|
||||
# Kredits Contracts
|
||||
|
||||
This repository contains the Solidity smart contracts organized as
|
||||
[Aragon](https://hack.aragon.org/) apps and JavaScript API wrapper for [Kosmos
|
||||
Kredits](https://wiki.kosmos.org/Kredits).
|
||||
|
||||
It is based on [aragonOS](https://hack.aragon.org/docs/aragonos-intro.html) and
|
||||
follows the aragonOS conventions. Aragon itself uses the [Truffle
|
||||
framework](http://truffleframework.com/) for some things.
|
||||
This repository contains the Solidity smart contracts and the JavaScript API
|
||||
wrapper for [Kosmos Kredits](https://wiki.kosmos.org/Kredits).
|
||||
|
||||
## Development
|
||||
|
||||
@ -20,226 +16,143 @@ All requirements are defined in `package.json`.
|
||||
|
||||
$ npm install
|
||||
|
||||
Each of the aragon apps are separate packages:
|
||||
|
||||
$ cd apps/[app]
|
||||
$ npm install
|
||||
|
||||
You can use `npm run install-all` to install all app dependencies at once.
|
||||
|
||||
#### Sytem dependencies
|
||||
|
||||
Aragon CLI and Truffle need to be installed on your sytem as well:
|
||||
|
||||
npm install -g @aragon/cli
|
||||
npm install -g truffle
|
||||
|
||||
### Local development chain
|
||||
|
||||
For local development it is recommended to use
|
||||
[ganache](http://truffleframework.com/ganache/) to run a local development
|
||||
chain. When using the ganache simulator, no full Ethereum node is required.
|
||||
We use [hardhat](https://hardhat.org/) as development environment for the
|
||||
smart contracts.
|
||||
|
||||
We use the default aragon-cli devchain command to configure and run a local
|
||||
development ganache.
|
||||
To run a local development chain run:
|
||||
|
||||
$ npm run devchain (or aragon devchain --port 7545)
|
||||
|
||||
To clear/reset the chain use (e.g. if you run out of funds on your devchain)
|
||||
|
||||
$ npm run devchain -- --reset (or aragon devchain --port 7545 --reset)
|
||||
|
||||
We default to port 7545 for development to not get in conflict with the default
|
||||
Ethereum RPC port.
|
||||
|
||||
You can also set certain ganache options to configure the devchain, for example
|
||||
if you want to increase the block time to 10 seconds you can add
|
||||
`--block-time=10`.
|
||||
$ npm run devchain # or: hardhat node --network hardhat
|
||||
|
||||
### Bootstrap
|
||||
|
||||
1. Run an Ethereum node and ipfs
|
||||
1. Run an EVM node and ipfs
|
||||
|
||||
$ npm run devchain
|
||||
$ ipfs daemon
|
||||
|
||||
2. Compile contracts
|
||||
2. Compile contracts and build ABIs
|
||||
|
||||
(compiled contracts will be in `/build`)
|
||||
$ npm run compile-contracts
|
||||
(compiled artifacts will be in `/artifacts`)
|
||||
$ npm run build
|
||||
|
||||
3. Deploy each app to the devchain
|
||||
3. Deploy new upgradable contract proxies
|
||||
|
||||
(make sure you've run `npm install` for every app - see installation)
|
||||
$ npm run deploy:apps
|
||||
|
||||
4. Deploy a new KreditsKit and create a new DAO with the latest app versions
|
||||
|
||||
$ npm run deploy:kit
|
||||
$ npm run deploy:dao
|
||||
|
||||
5. Execute seeds to create demo contributors, contributions, etc. (optional)
|
||||
4. Execute seeds to create demo contributors, contributions, etc. (optional)
|
||||
|
||||
$ npm run seeds
|
||||
|
||||
**Step 2-5 is also summarized in `npm run bootstrap`**
|
||||
**Step 2-4 is also summarized in `npm run bootstrap`**
|
||||
|
||||
If you want to reset your local setup:
|
||||
5. Show contract addresses
|
||||
|
||||
$ npm run reset // deploys a new kit and a new DAO
|
||||
$ npm run reset:hard // deploys all apps and does reset
|
||||
$ cat lib/addresses.json
|
||||
|
||||
## Fund a local development account
|
||||
|
||||
If you need to fund development accounts with devchain coins:
|
||||
|
||||
$ npm run fund # or hardhat fund --network localhost
|
||||
|
||||
## Specs / Testing
|
||||
|
||||
With a local development chain running:
|
||||
|
||||
$ hardhat test
|
||||
|
||||
If you add or change contract code, please make sure to add and/or adapt tests
|
||||
accordingly. Don't worry, it's easy! You can use existing tests as a template
|
||||
for new ones.
|
||||
|
||||
## Contract architecture
|
||||
|
||||
Contracts are organized in independent apps (see `/apps`) and are developed and
|
||||
deployed independently. Each app has a version and can be "installed" on the
|
||||
Kredits DAO independently.
|
||||
|
||||

|
||||
|
||||
A DAO can be deployed using the `scripts/deploy-kit.js` script or with the
|
||||
`npm run deploy:dao` command. This deploys a new Kredits DAO, installs the
|
||||
latest app versions and sets the required permissions.
|
||||
|
||||
See each app in `/apps/*` for details.
|
||||
We use the [OpenZeppelin hardhat
|
||||
proxy](https://www.npmjs.com/package/@openzeppelin/hardhat-upgrades) for
|
||||
deploying and managing upgradeable contracts. (see `scripts/create-proxy.js`)
|
||||
|
||||
Each contract is independent and is connected to its dependencies by storing
|
||||
the addresses of the other contracts.
|
||||
|
||||
## Helper scripts
|
||||
|
||||
`scripts/` contains some helper scripts to interact with the contracts from the
|
||||
CLI. _At some point these should be moved into a real nice CLI._
|
||||
|
||||
To run these scripts use `truffle exec`. For example: `truffle exec
|
||||
scripts/add-proposal.js`.
|
||||
To run these scripts use `hardhat run`. For example: `hardhat run
|
||||
scripts/list-contributors.js --network localhost`. (NOTE: add `--network
|
||||
localhost` or the network you want to use)
|
||||
|
||||
Some scripts are also defined as npm script, see package.json.
|
||||
Some scripts are also defined as npm script, see `package.json`.
|
||||
|
||||
### cli.js
|
||||
|
||||
Call any function on any contract:
|
||||
|
||||
$ truffle exec scripts/cli.js
|
||||
|
||||
### repl.js
|
||||
### repl/console
|
||||
|
||||
Similar to cli.js but only provides a REPL with an initialized `kredits`
|
||||
instance.
|
||||
|
||||
$ truffle exec scripts/repl.js
|
||||
$ hardhat console --network localhost
|
||||
|
||||
### add-{contributor, contribution, proposal}.js
|
||||
|
||||
Script to add a new entries to the contracts using the JS wrapper
|
||||
|
||||
$ truffle exec scripts/add-{contributor, contribution, proposal}.js
|
||||
$ hardhat run scripts/add-{contributor, contribution, proposal}.js --network localhost
|
||||
|
||||
### list-{contributors, contributions, proposals}.js
|
||||
|
||||
List contract entries
|
||||
|
||||
$ truffle exec scripts/list-{contributors, contributions, proposals}.js
|
||||
|
||||
### send-funds.js
|
||||
|
||||
Sends funds to an address. Helpful in development mode to for example fund a
|
||||
metamask account.
|
||||
|
||||
$ truffle exec scripts/send-funds.js
|
||||
$ hardhat run scripts/list-{contributors, contributions, proposals}.js --network localhost
|
||||
|
||||
### seeds.js
|
||||
|
||||
Run seeds defined in `config/seeds.js`.
|
||||
|
||||
$ truffle exec scripts/seeds.js
|
||||
or
|
||||
$ npm run seeds
|
||||
|
||||
### current-address.js
|
||||
### Get the contract addresses
|
||||
|
||||
Prints all known DAO addresses and the DAO address for the current network
|
||||
All contract addresses are stored in `lib/addresses.json`
|
||||
|
||||
$ truffle exec scripts/current-address.js
|
||||
or
|
||||
$ npm run dao:address
|
||||
|
||||
### deploy-kit.js
|
||||
|
||||
Deploys a new KreditsKit that allows to create a new DAO
|
||||
|
||||
$ truffle exec script/deploy-kit.js
|
||||
or
|
||||
$ npm run deploy:kit
|
||||
|
||||
#### Kredits configuration options:
|
||||
|
||||
Configuration options can be set in an environment specific `kredits` object in the `arapp.json` or using a CLI parameter.
|
||||
|
||||
* daoFactory: Ethereum address of the used DAO Factory. On public networks we use [official aragon factories](https://github.com/aragon/deployments/tree/master/environments/)
|
||||
* apmDomain: the ENS domain of the aragonPM (normally `open.aragonpm.eth`)
|
||||
|
||||
(please also see the [arapp.json related configuration options](https://hack.aragon.org/docs/cli-global-confg#the-arappjson-file))
|
||||
|
||||
### new-dao.js
|
||||
|
||||
Creates and configures a new DAO instance.
|
||||
|
||||
$ truffle exec script/new-dao.js
|
||||
or
|
||||
$ npm run deploy:dao
|
||||
|
||||
KreditsKit address is loaded from `lib/addresses/KreditsKit.json` or can be
|
||||
configured through the `KREDITS_KIT` environment variable.
|
||||
|
||||
### deploy-apps.sh
|
||||
|
||||
Runs `npm install` for each app and publishes a new version.
|
||||
|
||||
$ ./scripts/deploy-apps.sh
|
||||
or
|
||||
$ npm run deploy:apps
|
||||
|
||||
## Deployment
|
||||
|
||||
### Apps deployment
|
||||
|
||||
To deploy a new app version run:
|
||||
|
||||
$ aragon apm publish major --environment=NETWORK_TO_DEPLOY
|
||||
|
||||
### KreditsKit
|
||||
|
||||
deploy the KreditsKit as Kit to create new DAOs
|
||||
|
||||
$ truffle exec scripts/deploy-kit.js --network=NETWORK_TO_DEPLOY
|
||||
|
||||
### Creating a new DAO
|
||||
|
||||
make sure all apps and the KreditsKit are deployed, then create a new DAO:
|
||||
|
||||
$ truffle exec scripts/new-dao.js --network=NETWORK_TO_DEPLOY
|
||||
|
||||
## ACL / Permissions
|
||||
$ cat lib/addresses.json
|
||||
|
||||
## Upgradeable contracts
|
||||
|
||||
We use aragonOS for upgradeability of the different contracts. Refer to the
|
||||
[aragonOS upgradeablity documentation](https://hack.aragon.org/docs/upgradeability-intro)
|
||||
for more details.
|
||||
We use OpenZeppelin for an upgradeable contracts:
|
||||
[https://www.npmjs.com/package/@openzeppelin/hardhat-upgrades](https://www.npmjs.com/package/@openzeppelin/hardhat-upgrades)
|
||||
|
||||
### Example
|
||||
Refer to the OpenZeppelin README and `scripts/create-proxy.js`
|
||||
|
||||
1. Setup (see #Bootstrap)
|
||||
1. Deploy each contract/apps (see `/apps/*`)
|
||||
2. Create a new DAO (see scripts/deploy-kit.js)
|
||||
2. Update
|
||||
1. Deploy a new Version of the contract/app (see `/apps/*`)
|
||||
2. Use the `aragon dao upgrade` command to "install" the new version for the DAO
|
||||
(`aragon dao upgrade <DAO address> <app name>`)
|
||||
[OpenZeppelin Step by Step guide](https://forum.openzeppelin.com/t/openzeppelin-upgrades-step-by-step-tutorial-for-hardhat/3580)
|
||||
|
||||
## Known Issues
|
||||
For an upgrade example checkout `scripts/upgrade-example.js`
|
||||
|
||||
When resetting ganache Metamask might have an invalid transaction nonce and
|
||||
transactions get rejected. Nonces in Ethereum must be incrementing and have no
|
||||
gap.
|
||||
|
||||
To solve this reset the metamask account (Account -> Settings -> Reset Account)
|
||||
## Deployment to other networks
|
||||
|
||||
Deployable networks are configured in the `hardhat.config.js`.
|
||||
|
||||
To deploy to those networks provide the `--network` argument to the hardhat
|
||||
commands, e.g. `--network rsk`.
|
||||
|
||||
Please note that some npm scripts combine multiple hardhat commands. In those
|
||||
cases the hardhat commands needs to be run manually with the `--network`
|
||||
argument. (=> don't use `npm run bootstrap`)
|
||||
|
||||
Set a `DEPLOY_KEY` environment variable with the private key (HEX) which will
|
||||
be used as a root/deploy account
|
||||
|
||||
Typical deployment flow:
|
||||
|
||||
$ npm run build
|
||||
$ hardhat run scripts/create-proxy.js --network rsk
|
||||
# OR with deploy key:
|
||||
$ DEPLOY_KEY=0xsomething hardhat run scripts/create-proxy.js --network rsk
|
||||
$ # commit the new addresses in the addresses.json file if needed
|
||||
|
||||
To run the console on one of the non localhost networks you can also just pass
|
||||
on the --network argument.
|
||||
|
||||
$ hardhat console --network rsk
|
||||
|
@ -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,155 +0,0 @@
|
||||
pragma solidity ^0.4.24;
|
||||
|
||||
import "@aragon/os/contracts/apps/AragonApp.sol";
|
||||
import "@aragon/os/contracts/kernel/IKernel.sol";
|
||||
|
||||
interface ITokenBalance {
|
||||
function balanceOf(address contributorAccount) public view returns (uint256);
|
||||
}
|
||||
interface IContributionBalance {
|
||||
function totalKreditsEarnedByContributor(uint32 contributorId, bool confirmedOnly) public view returns (uint32 amount);
|
||||
function balanceOf(address owner) public view returns (uint256);
|
||||
}
|
||||
|
||||
contract Contributor is AragonApp {
|
||||
bytes32 public constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb;
|
||||
bytes32 public constant MANAGE_CONTRIBUTORS_ROLE = keccak256("MANAGE_CONTRIBUTORS_ROLE");
|
||||
|
||||
struct Contributor {
|
||||
address account;
|
||||
bytes32 hashDigest;
|
||||
uint8 hashFunction;
|
||||
uint8 hashSize;
|
||||
bool exists;
|
||||
}
|
||||
|
||||
mapping (address => uint32) public contributorIds;
|
||||
mapping (uint32 => Contributor) public contributors;
|
||||
uint32 public contributorsCount;
|
||||
|
||||
// ensure alphabetic order
|
||||
enum Apps { Contribution, Contributor, Proposal, Token }
|
||||
bytes32[4] public appIds;
|
||||
|
||||
event ContributorProfileUpdated(uint32 id, bytes32 oldHashDigest, bytes32 newHashDigest); // what should be logged
|
||||
event ContributorAccountUpdated(uint32 id, address oldAccount, address newAccount);
|
||||
event ContributorAdded(uint32 id, address account);
|
||||
|
||||
function initialize(address root,bytes32[4] _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 coreContributorsCount() public view returns (uint32) {
|
||||
uint32 count = 0;
|
||||
for (uint32 i = 1; i <= contributorsCount; i++) {
|
||||
if (isCoreTeam(i)) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function updateContributorAccount(uint32 id, address oldAccount, address newAccount) public auth(MANAGE_CONTRIBUTORS_ROLE) {
|
||||
require(newAccount != address(0), "invalid new account address");
|
||||
require(getContributorAddressById(id) == oldAccount, "contributor does not exist");
|
||||
|
||||
contributorIds[oldAccount] = 0;
|
||||
contributorIds[newAccount] = id;
|
||||
contributors[id].account = newAccount;
|
||||
emit ContributorAccountUpdated(id, oldAccount, newAccount);
|
||||
}
|
||||
|
||||
function updateContributorProfileHash(uint32 id, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public isInitialized auth(MANAGE_CONTRIBUTORS_ROLE) {
|
||||
Contributor storage c = contributors[id];
|
||||
bytes32 oldHashDigest = c.hashDigest;
|
||||
c.hashDigest = hashDigest;
|
||||
c.hashFunction = hashFunction;
|
||||
c.hashSize = hashSize;
|
||||
|
||||
ContributorProfileUpdated(id, oldHashDigest, c.hashDigest);
|
||||
}
|
||||
|
||||
function addContributor(address account, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public isInitialized auth(MANAGE_CONTRIBUTORS_ROLE) {
|
||||
require(!addressExists(account));
|
||||
uint32 _id = contributorsCount + 1;
|
||||
assert(!contributors[_id].exists); // this can not be acually
|
||||
Contributor storage c = contributors[_id];
|
||||
c.exists = true;
|
||||
c.hashDigest = hashDigest;
|
||||
c.hashFunction = hashFunction;
|
||||
c.hashSize = hashSize;
|
||||
c.account = account;
|
||||
contributorIds[account] = _id;
|
||||
|
||||
contributorsCount += 1;
|
||||
emit ContributorAdded(_id, account);
|
||||
}
|
||||
|
||||
function isCoreTeam(uint32 id) view public returns (bool) {
|
||||
// TODO: for simplicity we simply define the first contributors as core
|
||||
// later this needs to be changed to something more dynamic
|
||||
return id < 7;
|
||||
}
|
||||
|
||||
function exists(uint32 id) view public returns (bool) {
|
||||
return contributors[id].exists;
|
||||
}
|
||||
|
||||
function addressIsCore(address account) view public returns (bool) {
|
||||
uint32 id = getContributorIdByAddress(account);
|
||||
return isCoreTeam(id);
|
||||
}
|
||||
|
||||
function addressExists(address account) view public returns (bool) {
|
||||
return getContributorByAddress(account).exists;
|
||||
}
|
||||
|
||||
function getContributorIdByAddress(address account) view public returns (uint32) {
|
||||
return contributorIds[account];
|
||||
}
|
||||
|
||||
function getContributorAddressById(uint32 id) view public returns (address) {
|
||||
return contributors[id].account;
|
||||
}
|
||||
|
||||
function getContributorByAddress(address account) internal view returns (Contributor) {
|
||||
uint32 id = contributorIds[account];
|
||||
return contributors[id];
|
||||
}
|
||||
|
||||
function getContributorById(uint32 _id) public view returns (uint32 id, address account, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, bool isCore, uint256 balance, uint32 totalKreditsEarned, uint256 contributionsCount, bool exists ) {
|
||||
id = _id;
|
||||
Contributor storage c = contributors[_id];
|
||||
account = c.account;
|
||||
hashDigest = c.hashDigest;
|
||||
hashFunction = c.hashFunction;
|
||||
hashSize = c.hashSize;
|
||||
isCore = isCoreTeam(id);
|
||||
address token = getContract(uint8(Apps.Token));
|
||||
balance = ITokenBalance(token).balanceOf(c.account);
|
||||
address contribution = getContract(uint8(Apps.Contribution));
|
||||
totalKreditsEarned = IContributionBalance(contribution).totalKreditsEarnedByContributor(_id, true);
|
||||
contributionsCount = IContributionBalance(contribution).balanceOf(c.account);
|
||||
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,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, Token }
|
||||
bytes32[4] 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[4] _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");
|
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, Token }
|
||||
bytes32[4] public appIds;
|
||||
|
||||
event LogMint(address indexed recipient, uint256 amount, uint32 contributionId);
|
||||
|
||||
function initialize(bytes32[4] _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"
|
||||
}
|
@ -15,7 +15,7 @@ const contractCalls = [
|
||||
name: 'raucao',
|
||||
kind: 'person',
|
||||
url: '',
|
||||
github_username: 'skddc',
|
||||
github_username: 'raucao',
|
||||
github_uid: 842,
|
||||
gitea_username: 'raucao',
|
||||
wiki_username: 'Basti',
|
||||
@ -31,13 +31,56 @@ const contractCalls = [
|
||||
wiki_username: 'Manuel',
|
||||
}, { gasLimit: 200000 }]],
|
||||
|
||||
['Proposal', 'add', [{ contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF', date: '2019-04-09', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Ran the seeds', url: '' }, { gasLimit: 350000 }]],
|
||||
['Proposal', 'add', [{ contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-10', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Ran the seeds', url: '' }, { gasLimit: 350000 }]],
|
||||
['Proposal', 'add', [{ contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-11', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Hacked on kredits', url: '' }, { gasLimit: 350000 }]],
|
||||
['Proposal', 'vote', [1, { gasLimit: 550000 }]],
|
||||
['Contribution', 'add', [{ contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF', date: '2019-04-11', amount: 5000, kind: 'dev', description: '[67P/kredits-contracts] Introduce contribution token', url: '' }, { gasLimit: 350000 }]],
|
||||
['Contribution', 'add', [{ contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-11', amount: 1500, kind: 'dev', description: '[67P/kredits-web] Reviewed stuff', url: '' }, { gasLimit: 350000 }]],
|
||||
['Contribution', 'claim', [1, { gasLimit: 300000 }]],
|
||||
['Contribution', 'add', [{
|
||||
contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF',
|
||||
date: '2019-04-11', amount: 500, kind: 'dev',
|
||||
description: '[67P/kredits-contracts] Test this thing',
|
||||
url: '',
|
||||
confirmedAtBlock: 1,
|
||||
}, { gasLimit: 350000 }]],
|
||||
|
||||
['Contribution', 'add', [{
|
||||
contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB',
|
||||
date: '2019-04-11', amount: 1500, kind: 'dev',
|
||||
description: '[67P/kredits-web] Reviewed stuff',
|
||||
url: '',
|
||||
confirmedAtBlock: 1,
|
||||
}, { gasLimit: 350000 }]],
|
||||
|
||||
['Contribution', 'add', [{
|
||||
contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF',
|
||||
date: '2019-04-11', amount: 5000, kind: 'dev',
|
||||
description: '[67P/kredits-contracts] Add tests',
|
||||
url: '',
|
||||
confirmedAtBlock: 1,
|
||||
}, { gasLimit: 350000 }]],
|
||||
|
||||
['Contribution', 'add', [{
|
||||
contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF',
|
||||
date: '2019-04-11', amount: 1500, kind: 'dev',
|
||||
description: '[67P/kredits-contracts] Introduce contribution token',
|
||||
url: '',
|
||||
}, { gasLimit: 350000 }]],
|
||||
|
||||
['Contribution', 'add', [{
|
||||
contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB',
|
||||
date: '2019-04-11', amount: 1500, kind: 'design',
|
||||
description: '[67P/kredits-web] Expense UI, first draft',
|
||||
url: '',
|
||||
}, { gasLimit: 350000 }]],
|
||||
|
||||
['Reimbursement', 'add', [{ amount: 346800, recipientId: 2, token: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', expenses: [
|
||||
{ title: 'Domain kosmos.social', description: 'Yearly registration fee for domain kosmos.social', amount: 69.00, currency: 'EUR', amountSats: 69216, date: '2020-04-30' },
|
||||
], confirmedAtBlock: 1 }, { gasLimit: 300000 }]],
|
||||
|
||||
['Reimbursement', 'add', [{ amount: 1116000, recipientId: 1, token: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', expenses: [
|
||||
{ title: 'Server rent', description: 'Dedicated server: andromeda.kosmos.org, April 2020', amount: 61, currency: 'EUR', amountSats: 61191, date: '2020-05-28' },
|
||||
{ title: 'Server rent', description: 'Dedicated server: centaurus.kosmos.org, April 2020', amount: 32, currency: 'EUR', amountSats: 32201, date: '2020-05-28' },
|
||||
], confirmedAtBlock: 1 }, { gasLimit: 300000 }]],
|
||||
|
||||
['Reimbursement', 'add', [{ amount: 166800, recipientId: 2, token: '0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', expenses: [
|
||||
{ title: 'Domain kosmos.chat', description: 'Yearly registration fee for domain kosmos.chat', amount: 13.90, currency: 'EUR', amountSats: 13944, date: '2020-05-30' },
|
||||
]}, { gasLimit: 300000 }]],
|
||||
];
|
||||
|
||||
const funds = [
|
||||
|
@ -1,33 +1,21 @@
|
||||
pragma solidity ^0.4.24;
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@aragon/os/contracts/apps/AragonApp.sol";
|
||||
import "@aragon/os/contracts/kernel/IKernel.sol";
|
||||
|
||||
interface IToken {
|
||||
function mintFor(address contributorAccount, uint256 amount, uint32 contributionId) public;
|
||||
}
|
||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||
|
||||
interface ContributorInterface {
|
||||
function getContributorAddressById(uint32 contributorId) public view returns (address);
|
||||
function getContributorIdByAddress(address contributorAccount) public view returns (uint32);
|
||||
function getContributorAddressById(uint32 contributorId) external view returns (address);
|
||||
function getContributorIdByAddress(address contributorAccount) external view returns (uint32);
|
||||
function addressIsCore(address sender) external view returns (bool);
|
||||
// TODO Maybe use for validation
|
||||
// function exists(uint32 contributorId) public view returns (bool);
|
||||
}
|
||||
|
||||
contract Contribution is AragonApp {
|
||||
bytes32 public constant ADD_CONTRIBUTION_ROLE = keccak256("ADD_CONTRIBUTION_ROLE");
|
||||
bytes32 public constant VETO_CONTRIBUTION_ROLE = keccak256("VETO_CONTRIBUTION_ROLE");
|
||||
|
||||
bytes32 public constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb;
|
||||
|
||||
// ensure alphabetic order
|
||||
enum Apps { Contribution, Contributor, Proposal, Token }
|
||||
bytes32[4] public appIds;
|
||||
contract Contribution is Initializable {
|
||||
ContributorInterface public contributorContract;
|
||||
|
||||
struct ContributionData {
|
||||
uint32 contributorId;
|
||||
uint32 amount;
|
||||
bool claimed;
|
||||
bytes32 hashDigest;
|
||||
uint8 hashFunction;
|
||||
uint8 hashSize;
|
||||
@ -48,54 +36,72 @@ contract Contribution is AragonApp {
|
||||
mapping(uint32 => ContributionData) public contributions;
|
||||
uint32 public contributionsCount;
|
||||
|
||||
// Confirmation veto period
|
||||
uint32 public blocksToWait;
|
||||
|
||||
// The address that deployed the contract
|
||||
address public deployer;
|
||||
|
||||
// Data migration flag
|
||||
bool public migrationDone;
|
||||
|
||||
event ContributionAdded(uint32 id, uint32 indexed contributorId, uint32 amount);
|
||||
event ContributionClaimed(uint32 id, uint32 indexed contributorId, uint32 amount);
|
||||
event ContributionVetoed(uint32 id, address vetoedByAccount);
|
||||
|
||||
function initialize(bytes32[4] _appIds) public onlyInit {
|
||||
appIds = _appIds;
|
||||
blocksToWait = 40320; // 7 days; 15 seconds block time
|
||||
initialized();
|
||||
modifier onlyCore {
|
||||
require(contributorContract.addressIsCore(tx.origin), "Core only");
|
||||
_;
|
||||
}
|
||||
|
||||
function getContract(uint8 appId) public view returns (address) {
|
||||
IKernel k = IKernel(kernel());
|
||||
return k.getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[appId]);
|
||||
modifier onlyDeployer {
|
||||
require(msg.sender == deployer, "Deployer only");
|
||||
_;
|
||||
}
|
||||
|
||||
function initialize(uint32 blocksToWait_) public initializer {
|
||||
deployer = msg.sender;
|
||||
migrationDone = false;
|
||||
blocksToWait = blocksToWait_;
|
||||
}
|
||||
|
||||
function finishMigration() public onlyDeployer {
|
||||
migrationDone = true;
|
||||
}
|
||||
|
||||
function setContributorContract(address contributor) public {
|
||||
require(address(contributorContract) == address(0) || contributorContract.addressIsCore(msg.sender), "Core only");
|
||||
contributorContract = ContributorInterface(contributor);
|
||||
}
|
||||
|
||||
function getContributorIdByAddress(address contributorAccount) public view returns (uint32) {
|
||||
address contributor = getContract(uint8(Apps.Contributor));
|
||||
return ContributorInterface(contributor).getContributorIdByAddress(contributorAccount);
|
||||
return contributorContract.getContributorIdByAddress(contributorAccount);
|
||||
}
|
||||
|
||||
function getContributorAddressById(uint32 contributorId) public view returns (address) {
|
||||
address contributor = getContract(uint8(Apps.Contributor));
|
||||
return ContributorInterface(contributor).getContributorAddressById(contributorId);
|
||||
return contributorContract.getContributorAddressById(contributorId);
|
||||
}
|
||||
|
||||
//
|
||||
// Token standard functions (ERC 721)
|
||||
//
|
||||
|
||||
function name() external view returns (string) {
|
||||
function name() external view returns (string memory) {
|
||||
return name_;
|
||||
}
|
||||
|
||||
function symbol() external view returns (string) {
|
||||
function symbol() external view returns (string memory) {
|
||||
return symbol_;
|
||||
}
|
||||
|
||||
// Balance is amount of ERC271 tokens, not amount of kredits
|
||||
function balanceOf(address owner) public view returns (uint256) {
|
||||
require(owner != address(0));
|
||||
require(owner != address(0), "Address invalid");
|
||||
uint32 contributorId = getContributorIdByAddress(owner);
|
||||
return ownedContributions[contributorId].length;
|
||||
}
|
||||
|
||||
function ownerOf(uint32 contributionId) public view returns (address) {
|
||||
require(exists(contributionId));
|
||||
require(exists(contributionId), "Contribution does not exist");
|
||||
uint32 contributorId = contributions[contributionId].contributorId;
|
||||
return getContributorAddressById(contributorId);
|
||||
}
|
||||
@ -105,7 +111,7 @@ contract Contribution is AragonApp {
|
||||
return ownedContributions[contributorId][index];
|
||||
}
|
||||
|
||||
function tokenMetadata(uint32 contributionId) public view returns (string) {
|
||||
function tokenMetadata(uint32 contributionId) public view returns (string memory) {
|
||||
return contributions[contributionId].tokenMetadataURL;
|
||||
}
|
||||
|
||||
@ -133,14 +139,13 @@ contract Contribution is AragonApp {
|
||||
}
|
||||
}
|
||||
|
||||
function getContribution(uint32 contributionId) public view returns (uint32 id, uint32 contributorId, uint32 amount, bool claimed, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, uint256 confirmedAtBlock, bool exists, bool vetoed) {
|
||||
function getContribution(uint32 contributionId) public view returns (uint32 id, uint32 contributorId, uint32 amount, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, uint256 confirmedAtBlock, bool exists, bool vetoed) {
|
||||
id = contributionId;
|
||||
ContributionData storage c = contributions[id];
|
||||
return (
|
||||
id,
|
||||
c.contributorId,
|
||||
c.amount,
|
||||
c.claimed,
|
||||
c.hashDigest,
|
||||
c.hashFunction,
|
||||
c.hashSize,
|
||||
@ -150,23 +155,27 @@ contract Contribution is AragonApp {
|
||||
);
|
||||
}
|
||||
|
||||
function add(uint32 amount, uint32 contributorId, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public isInitialized auth(ADD_CONTRIBUTION_ROLE) {
|
||||
//require(canPerform(msg.sender, ADD_CONTRIBUTION_ROLE, new uint32[](0)), 'nope');
|
||||
function add(uint32 amount, uint32 contributorId, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, uint256 confirmedAtBlock, bool vetoed) public {
|
||||
require((confirmedAtBlock == 0 && vetoed == false) || migrationDone == false, "Extra arguments not allowed");
|
||||
require(balanceOf(msg.sender) > 0 || contributorContract.addressIsCore(msg.sender), "Requires kredits or core status");
|
||||
|
||||
uint32 contributionId = contributionsCount + 1;
|
||||
ContributionData storage c = contributions[contributionId];
|
||||
c.exists = true;
|
||||
c.amount = amount;
|
||||
c.claimed = false;
|
||||
c.contributorId = contributorId;
|
||||
c.hashDigest = hashDigest;
|
||||
c.hashFunction = hashFunction;
|
||||
c.hashSize = hashSize;
|
||||
if (contributionId < 10) {
|
||||
c.confirmedAtBlock = block.number;
|
||||
|
||||
if (confirmedAtBlock > 0) {
|
||||
c.confirmedAtBlock = confirmedAtBlock;
|
||||
} else {
|
||||
c.confirmedAtBlock = block.number + blocksToWait;
|
||||
c.confirmedAtBlock = block.number + 1 + blocksToWait;
|
||||
}
|
||||
|
||||
if (vetoed) { c.vetoed = true; }
|
||||
|
||||
contributionsCount++;
|
||||
|
||||
contributionOwner[contributionId] = contributorId;
|
||||
@ -175,31 +184,15 @@ contract Contribution is AragonApp {
|
||||
emit ContributionAdded(contributionId, contributorId, amount);
|
||||
}
|
||||
|
||||
function veto(uint32 contributionId) public isInitialized auth(VETO_CONTRIBUTION_ROLE) {
|
||||
function veto(uint32 contributionId) public onlyCore {
|
||||
ContributionData storage c = contributions[contributionId];
|
||||
require(c.exists, 'NOT_FOUND');
|
||||
require(!c.claimed, 'ALREADY_CLAIMED');
|
||||
require(block.number < c.confirmedAtBlock, 'VETO_PERIOD_ENDED');
|
||||
require(c.exists, "NOT_FOUND");
|
||||
require(block.number < c.confirmedAtBlock, "VETO_PERIOD_ENDED");
|
||||
c.vetoed = true;
|
||||
|
||||
emit ContributionVetoed(contributionId, msg.sender);
|
||||
}
|
||||
|
||||
function claim(uint32 contributionId) public isInitialized {
|
||||
ContributionData storage c = contributions[contributionId];
|
||||
require(c.exists, 'NOT_FOUND');
|
||||
require(!c.claimed, 'ALREADY_CLAIMED');
|
||||
require(!c.vetoed, 'VETOED');
|
||||
require(block.number >= c.confirmedAtBlock, 'NOT_CLAIMABLE');
|
||||
|
||||
c.claimed = true;
|
||||
address token = getContract(uint8(Apps.Token));
|
||||
address contributorAccount = getContributorAddressById(c.contributorId);
|
||||
uint256 amount = uint256(c.amount);
|
||||
IToken(token).mintFor(contributorAccount, amount, contributionId);
|
||||
emit ContributionClaimed(contributionId, c.contributorId, c.amount);
|
||||
}
|
||||
|
||||
function exists(uint32 contributionId) public view returns (bool) {
|
||||
return contributions[contributionId].exists;
|
||||
}
|
178
contracts/Contributor.sol
Normal file
178
contracts/Contributor.sol
Normal file
@ -0,0 +1,178 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||
|
||||
interface IToken {
|
||||
function mintFor(address contributorAccount, uint256 amount) external;
|
||||
function balanceOf(address contributorAccount) external view returns (uint256);
|
||||
}
|
||||
interface IContributionBalance {
|
||||
function totalKreditsEarnedByContributor(uint32 contributorId, bool confirmedOnly) external view returns (uint32 amount);
|
||||
function balanceOf(address owner) external view returns (uint256);
|
||||
}
|
||||
|
||||
contract Contributor is Initializable {
|
||||
address public deployer;
|
||||
IContributionBalance public contributionContract;
|
||||
IToken public tokenContract;
|
||||
|
||||
struct Contributor {
|
||||
address account;
|
||||
bytes32 hashDigest;
|
||||
uint8 hashFunction;
|
||||
uint8 hashSize;
|
||||
bool exists;
|
||||
uint32 kreditsWithdrawn;
|
||||
}
|
||||
|
||||
mapping (address => uint32) public contributorIds;
|
||||
mapping (uint32 => Contributor) public contributors;
|
||||
uint32 public contributorsCount;
|
||||
|
||||
address public profileManager;
|
||||
|
||||
event ContributorProfileUpdated(uint32 id, bytes32 oldHashDigest, bytes32 newHashDigest); // what should be logged
|
||||
event ContributorAccountUpdated(uint32 id, address oldAccount, address newAccount);
|
||||
event ContributorAdded(uint32 id, address account);
|
||||
|
||||
modifier onlyCore {
|
||||
require(addressIsCore(tx.origin), "Core only");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyContributors {
|
||||
require(addressExists(msg.sender) && contributionContract.balanceOf(msg.sender) > 0, "Contributors only");
|
||||
_;
|
||||
}
|
||||
|
||||
function initialize(address profileManagerAddress) public initializer {
|
||||
deployer = msg.sender;
|
||||
profileManager = profileManagerAddress;
|
||||
}
|
||||
|
||||
function reinitialize(address profileManagerAddress) public reinitializer(2) {
|
||||
profileManager = profileManagerAddress;
|
||||
}
|
||||
|
||||
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 = IToken(token);
|
||||
}
|
||||
|
||||
function coreContributorsCount() public view returns (uint32) {
|
||||
uint32 count = 0;
|
||||
for (uint32 i = 1; i <= contributorsCount; i++) {
|
||||
if (isCoreTeam(i)) {
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
function updateContributorAccount(uint32 id, address oldAccount, address newAccount) public onlyCore {
|
||||
require(newAccount != address(0), "invalid new account address");
|
||||
require(getContributorAddressById(id) == oldAccount, "contributor does not exist");
|
||||
|
||||
contributorIds[oldAccount] = 0;
|
||||
contributorIds[newAccount] = id;
|
||||
contributors[id].account = newAccount;
|
||||
emit ContributorAccountUpdated(id, oldAccount, newAccount);
|
||||
}
|
||||
|
||||
function updateContributorProfileHash(uint32 id, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public onlyCore {
|
||||
Contributor storage c = contributors[id];
|
||||
bytes32 oldHashDigest = c.hashDigest;
|
||||
c.hashDigest = hashDigest;
|
||||
c.hashFunction = hashFunction;
|
||||
c.hashSize = hashSize;
|
||||
|
||||
emit ContributorProfileUpdated(id, oldHashDigest, c.hashDigest);
|
||||
}
|
||||
|
||||
function addContributor(address account, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public {
|
||||
require(!addressExists(account), "Address already in use");
|
||||
require((msg.sender == profileManager) || addressIsCore(msg.sender), "Only core and profile manager");
|
||||
uint32 _id = contributorsCount + 1;
|
||||
assert(!contributors[_id].exists); // this can not be acually
|
||||
Contributor storage c = contributors[_id];
|
||||
c.exists = true;
|
||||
c.hashDigest = hashDigest;
|
||||
c.hashFunction = hashFunction;
|
||||
c.hashSize = hashSize;
|
||||
c.account = account;
|
||||
c.kreditsWithdrawn = 0;
|
||||
contributorIds[account] = _id;
|
||||
|
||||
contributorsCount += 1;
|
||||
emit ContributorAdded(_id, account);
|
||||
}
|
||||
|
||||
function isCoreTeam(uint32 id) public view returns (bool) {
|
||||
// TODO: for simplicity we simply define the first contributors as core
|
||||
// later this needs to be changed to something more dynamic
|
||||
return id > 0 && id < 7;
|
||||
}
|
||||
|
||||
function exists(uint32 id) public view returns (bool) {
|
||||
return contributors[id].exists;
|
||||
}
|
||||
|
||||
function addressIsCore(address account) public view returns (bool) {
|
||||
// the deployer is always core
|
||||
if(account == deployer) {
|
||||
return true;
|
||||
}
|
||||
uint32 id = getContributorIdByAddress(account);
|
||||
return isCoreTeam(id);
|
||||
}
|
||||
|
||||
function addressExists(address account) public view returns (bool) {
|
||||
return getContributorByAddress(account).exists;
|
||||
}
|
||||
|
||||
function getContributorIdByAddress(address account) public view returns (uint32) {
|
||||
return contributorIds[account];
|
||||
}
|
||||
|
||||
function getContributorAddressById(uint32 id) public view returns (address) {
|
||||
return contributors[id].account;
|
||||
}
|
||||
|
||||
function getContributorByAddress(address account) internal view returns (Contributor memory) {
|
||||
uint32 id = contributorIds[account];
|
||||
return contributors[id];
|
||||
}
|
||||
|
||||
function getContributorById(uint32 _id) public view returns (uint32 id, address account, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, bool isCore, uint256 balance, uint32 totalKreditsEarned, uint256 contributionsCount, bool exists, uint256 kreditsWithdrawn) {
|
||||
id = _id;
|
||||
Contributor storage c = contributors[_id];
|
||||
account = c.account;
|
||||
hashDigest = c.hashDigest;
|
||||
hashFunction = c.hashFunction;
|
||||
hashSize = c.hashSize;
|
||||
isCore = isCoreTeam(id);
|
||||
balance = tokenContract.balanceOf(c.account);
|
||||
totalKreditsEarned = contributionContract.totalKreditsEarnedByContributor(_id, true);
|
||||
contributionsCount = contributionContract.balanceOf(c.account);
|
||||
exists = c.exists;
|
||||
kreditsWithdrawn = c.kreditsWithdrawn;
|
||||
}
|
||||
|
||||
function withdraw() public onlyContributors {
|
||||
uint32 id = getContributorIdByAddress(msg.sender);
|
||||
Contributor storage c = contributors[id];
|
||||
|
||||
// TODO check if we need a failsafe for unconfirmed or malicious txs
|
||||
uint32 confirmedKredits = contributionContract.totalKreditsEarnedByContributor(id, true);
|
||||
uint32 amountWithdrawable = confirmedKredits - c.kreditsWithdrawn;
|
||||
require (amountWithdrawable > 0, "No kredits available");
|
||||
|
||||
c.kreditsWithdrawn += amountWithdrawable;
|
||||
tokenContract.mintFor(msg.sender, amountWithdrawable);
|
||||
}
|
||||
}
|
@ -1,90 +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";
|
||||
|
||||
contract KreditsKit is KitBase {
|
||||
|
||||
// ensure alphabetic order
|
||||
enum Apps { Contribution, Contributor, Proposal, Token }
|
||||
bytes32[4] public appIds;
|
||||
|
||||
event DeployInstance(address dao);
|
||||
event InstalledApp(address dao, address appProxy, bytes32 appId);
|
||||
|
||||
constructor (DAOFactory _fac, ENS _ens, bytes32[4] _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);
|
||||
|
||||
Proposal proposal = Proposal(_installApp(dao, appIds[uint8(Apps.Proposal)]));
|
||||
proposal.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());
|
||||
|
||||
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.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.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;
|
||||
}
|
||||
}
|
139
contracts/Reimbursement.sol
Normal file
139
contracts/Reimbursement.sol
Normal file
@ -0,0 +1,139 @@
|
||||
pragma solidity ^0.8.0;
|
||||
|
||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.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 Reimbursement is Initializable {
|
||||
ContributorInterface public contributorContract;
|
||||
|
||||
struct ReimbursementData {
|
||||
uint32 recipientId;
|
||||
uint256 amount;
|
||||
// TODO remove token entirely
|
||||
address token;
|
||||
bytes32 hashDigest;
|
||||
uint8 hashFunction;
|
||||
uint8 hashSize;
|
||||
uint256 confirmedAtBlock;
|
||||
bool vetoed;
|
||||
bool exists;
|
||||
}
|
||||
|
||||
mapping(uint32 => ReimbursementData) public reimbursements;
|
||||
uint32 public reimbursementsCount;
|
||||
|
||||
uint32 public blocksToWait;
|
||||
|
||||
// The address that deployed the contract
|
||||
address public deployer;
|
||||
|
||||
// Data migration flag
|
||||
bool public migrationDone;
|
||||
|
||||
event ReimbursementAdded(uint32 id, address indexed addedByAccount, uint256 amount);
|
||||
event ReimbursementVetoed(uint32 id, address vetoedByAccount);
|
||||
|
||||
modifier onlyCore {
|
||||
require(contributorContract.addressIsCore(tx.origin), "Core only");
|
||||
_;
|
||||
}
|
||||
|
||||
modifier onlyDeployer {
|
||||
require(msg.sender == deployer, "Deployer only");
|
||||
_;
|
||||
}
|
||||
|
||||
function initialize() public initializer {
|
||||
deployer = msg.sender;
|
||||
migrationDone = false;
|
||||
blocksToWait = 40320; // 7 days; 15 seconds block time
|
||||
}
|
||||
|
||||
function finishMigration() public onlyDeployer {
|
||||
migrationDone = true;
|
||||
}
|
||||
|
||||
function setContributorContract(address contributor) public {
|
||||
require(address(contributorContract) == address(0) || contributorContract.addressIsCore(msg.sender), "Core only");
|
||||
contributorContract = ContributorInterface(contributor);
|
||||
}
|
||||
|
||||
function getContributorIdByAddress(address contributorAccount) public view returns (uint32) {
|
||||
return contributorContract.getContributorIdByAddress(contributorAccount);
|
||||
}
|
||||
|
||||
function getContributorAddressById(uint32 contributorId) public view returns (address) {
|
||||
return contributorContract.getContributorAddressById(contributorId);
|
||||
}
|
||||
|
||||
function totalAmount(bool confirmedOnly) public view returns (uint256 amount) {
|
||||
for (uint32 i = 1; i <= reimbursementsCount; i++) {
|
||||
ReimbursementData memory r = reimbursements[i];
|
||||
if (!r.vetoed && (block.number >= r.confirmedAtBlock || !confirmedOnly)) {
|
||||
amount += r.amount; // should use safemath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function get(uint32 reimbursementId) public view returns (uint32 id, uint32 recipientId, uint256 amount, address token, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, uint256 confirmedAtBlock, bool exists, bool vetoed) {
|
||||
id = reimbursementId;
|
||||
ReimbursementData storage r = reimbursements[id];
|
||||
return (
|
||||
id,
|
||||
r.recipientId,
|
||||
r.amount,
|
||||
r.token,
|
||||
r.hashDigest,
|
||||
r.hashFunction,
|
||||
r.hashSize,
|
||||
r.confirmedAtBlock,
|
||||
r.exists,
|
||||
r.vetoed
|
||||
);
|
||||
}
|
||||
|
||||
function add(uint256 amount, address token, uint32 recipientId, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, uint256 confirmedAtBlock, bool vetoed) public onlyCore {
|
||||
require((confirmedAtBlock == 0 && vetoed == false) || migrationDone == false, "Extra arguments not allowed");
|
||||
uint32 reimbursementId = reimbursementsCount + 1;
|
||||
ReimbursementData storage r = reimbursements[reimbursementId];
|
||||
r.exists = true;
|
||||
r.amount = amount;
|
||||
r.token = token;
|
||||
r.recipientId = recipientId;
|
||||
r.hashDigest = hashDigest;
|
||||
r.hashFunction = hashFunction;
|
||||
r.hashSize = hashSize;
|
||||
|
||||
if (confirmedAtBlock > 0) {
|
||||
r.confirmedAtBlock = confirmedAtBlock;
|
||||
} else {
|
||||
r.confirmedAtBlock = block.number + 1 + blocksToWait;
|
||||
}
|
||||
|
||||
if (vetoed) { r.vetoed = true; }
|
||||
|
||||
reimbursementsCount++;
|
||||
|
||||
emit ReimbursementAdded(reimbursementId, msg.sender, amount);
|
||||
}
|
||||
|
||||
function veto(uint32 reimbursementId) public onlyCore {
|
||||
ReimbursementData storage r = reimbursements[reimbursementId];
|
||||
require(r.exists, "NOT_FOUND");
|
||||
require(block.number < r.confirmedAtBlock, "VETO_PERIOD_ENDED");
|
||||
r.vetoed = true;
|
||||
|
||||
emit ReimbursementVetoed(reimbursementId, msg.sender);
|
||||
}
|
||||
|
||||
function exists(uint32 reimbursementId) public view returns (bool) {
|
||||
return reimbursements[reimbursementId].exists;
|
||||
}
|
||||
}
|
41
contracts/Token.sol
Normal file
41
contracts/Token.sol
Normal file
@ -0,0 +1,41 @@
|
||||
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);
|
||||
}
|
||||
|
||||
contract Token is Initializable, ERC20Upgradeable {
|
||||
ContributorInterface public contributorContract;
|
||||
using SafeMathUpgradeable for uint256;
|
||||
|
||||
address public contributorContractAddress;
|
||||
|
||||
event KreditsMinted(address indexed recipient, uint256 amount);
|
||||
|
||||
function initialize() public virtual initializer {
|
||||
__ERC20_init("Kredits", "KS");
|
||||
}
|
||||
|
||||
function decimals() public view virtual override returns (uint8) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function setContributorContract(address contributor) public {
|
||||
require(address(contributorContract) == address(0) || contributorContract.addressIsCore(msg.sender), "Core only");
|
||||
contributorContract = ContributorInterface(contributor);
|
||||
contributorContractAddress = contributor;
|
||||
}
|
||||
|
||||
function mintFor(address contributorAccount, uint256 amount) public {
|
||||
require(contributorContractAddress == msg.sender, "Only Contributor");
|
||||
require(amount > 0, "INVALID_AMOUNT");
|
||||
|
||||
_mint(contributorAccount, amount);
|
||||
emit KreditsMinted(contributorAccount, amount);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -2,6 +2,36 @@
|
||||
|
||||
aragon apm publish major --environment=rinkeby"
|
||||
|
||||
## 20212-01-14
|
||||
|
||||
apps/contribution@master » aragon apm publish major --environment=rinkeby
|
||||
eth-provider | Invalid provider preset/location: "local"
|
||||
✔ Start IPFS
|
||||
✔ Applying version bump (major)
|
||||
↓ Building frontend [skipped]
|
||||
→ build script not defined in package.json
|
||||
✔ Deploy contract
|
||||
✔ Determine contract address for version
|
||||
✔ Prepare files for publishing
|
||||
✔ Generate application artifact
|
||||
✔ Publish intent
|
||||
|
||||
⚠ Publishing files from the project's root folder is not recommended. Consider using the distribution folder of your project: "--files <folder>".
|
||||
|
||||
The following information will be published:
|
||||
Contract address: 0x914Da982ef17B56D2e868E3a67E923EbED1aE017
|
||||
Content (ipfs): QmdVrY2R48NFqwLopd8ix1anAK1d6WafDGauou3ZJrB9gf
|
||||
|
||||
? Publish to kredits-contribution.open.aragonpm.eth repo Yes
|
||||
|
||||
✔ Publish kredits-contribution.open.aragonpm.eth
|
||||
|
||||
Successfully published kredits-contribution.open.aragonpm.eth v7.0.0 :
|
||||
|
||||
Transaction hash: 0xb817b2e80e90a6be60b45dd39987498e3132c9962c0501feb7549ad30186c6d5
|
||||
|
||||
|
||||
|
||||
## 2019-04-24 update balances
|
||||
|
||||
✔ Successfully published kredits-contribution.open.aragonpm.eth v6.0.0:
|
||||
|
@ -1,5 +1,16 @@
|
||||
# Kredits deployment
|
||||
|
||||
|
||||
## 2021-01-14
|
||||
|
||||
apps/contribution@master » aragon dao upgrade 0xc34edf7d11b7f8433d597f0bb0697acdff55ef14 kredits-contribution.open.aragonpm.eth --environment=rinkeby
|
||||
eth-provider | Invalid provider preset/location: "local"
|
||||
✔ Fetching kredits-contribution.open.aragonpm.eth@latest
|
||||
✔ Fetching kredits-contribution.open.aragonpm.eth@latest
|
||||
✔ Upgrading app
|
||||
|
||||
✔ Successfully executed: "Upgrade 'kredits-contribution.open.aragonpm.eth' app instances to v7.0.0"
|
||||
|
||||
## 2019-04-25 canPerfom fix
|
||||
|
||||
aragon dao upgrade 0xc34edf7d11b7f8433d597f0bb0697acdff55ef14 kredits-contributor.open.aragonpm.eth --environment=rinkeby
|
||||
|
90
hardhat.config.js
Normal file
90
hardhat.config.js
Normal file
@ -0,0 +1,90 @@
|
||||
require("@nomiclabs/hardhat-waffle");
|
||||
require("hardhat-deploy");
|
||||
require("hardhat-deploy-ethers");
|
||||
require("@nomicfoundation/hardhat-chai-matchers");
|
||||
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
1
lib/abis/Reimbursement.json
Normal file
1
lib/abis/Reimbursement.json
Normal file
@ -0,0 +1 @@
|
||||
[{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"version","type":"uint8"}],"name":"Initialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"id","type":"uint32"},{"indexed":true,"internalType":"address","name":"addedByAccount","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"ReimbursementAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint32","name":"id","type":"uint32"},{"indexed":false,"internalType":"address","name":"vetoedByAccount","type":"address"}],"name":"ReimbursementVetoed","type":"event"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"uint32","name":"recipientId","type":"uint32"},{"internalType":"bytes32","name":"hashDigest","type":"bytes32"},{"internalType":"uint8","name":"hashFunction","type":"uint8"},{"internalType":"uint8","name":"hashSize","type":"uint8"},{"internalType":"uint256","name":"confirmedAtBlock","type":"uint256"},{"internalType":"bool","name":"vetoed","type":"bool"}],"name":"add","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"blocksToWait","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"contributorContract","outputs":[{"internalType":"contract ContributorInterface","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"deployer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"reimbursementId","type":"uint32"}],"name":"exists","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"finishMigration","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint32","name":"reimbursementId","type":"uint32"}],"name":"get","outputs":[{"internalType":"uint32","name":"id","type":"uint32"},{"internalType":"uint32","name":"recipientId","type":"uint32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"bytes32","name":"hashDigest","type":"bytes32"},{"internalType":"uint8","name":"hashFunction","type":"uint8"},{"internalType":"uint8","name":"hashSize","type":"uint8"},{"internalType":"uint256","name":"confirmedAtBlock","type":"uint256"},{"internalType":"bool","name":"exists","type":"bool"},{"internalType":"bool","name":"vetoed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"contributorId","type":"uint32"}],"name":"getContributorAddressById","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contributorAccount","type":"address"}],"name":"getContributorIdByAddress","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"migrationDone","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"","type":"uint32"}],"name":"reimbursements","outputs":[{"internalType":"uint32","name":"recipientId","type":"uint32"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"token","type":"address"},{"internalType":"bytes32","name":"hashDigest","type":"bytes32"},{"internalType":"uint8","name":"hashFunction","type":"uint8"},{"internalType":"uint8","name":"hashSize","type":"uint8"},{"internalType":"uint256","name":"confirmedAtBlock","type":"uint256"},{"internalType":"bool","name":"vetoed","type":"bool"},{"internalType":"bool","name":"exists","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"reimbursementsCount","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contributor","type":"address"}],"name":"setContributorContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bool","name":"confirmedOnly","type":"bool"}],"name":"totalAmount","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"reimbursementId","type":"uint32"}],"name":"veto","outputs":[],"stateMutability":"nonpayable","type":"function"}]
|
File diff suppressed because one or more lines are too long
14
lib/addresses.json
Normal file
14
lib/addresses.json
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
"31": {
|
||||
"Contributor": "0x95DC31665D193E377f54b70C535fcDb205525291",
|
||||
"Contribution": "0x049bA8E70FEbFfd6d03C71211bDA37B4ff064115",
|
||||
"Token": "0x7ab26A0f00eF0D6e05e5BDE047505a4eD53aF809",
|
||||
"Reimbursement": "0x99EC72b34295b62f4bC1527Da461262c615a0b2c"
|
||||
},
|
||||
"1337": {
|
||||
"Contributor": "0xCc66f9A3cA2670972938FAD91d0865c4a62DFB25",
|
||||
"Contribution": "0x8999CaBc43E28202c5A2257f2a95A45b1F8A62BD",
|
||||
"Token": "0xe082678eCF749982e33Ea6839852a8cd989aEDE2",
|
||||
"Reimbursement": "0x984f797d26d3da2E9b9f8Ae4eeFEACC60fCAA90C"
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
get ipfs () {
|
||||
if (!this._ipfsAPI) { throw new Error('IPFS API not configured; please set an ipfs instance'); }
|
||||
return this._ipfsAPI;
|
||||
}
|
||||
|
||||
set ipfs (ipfsAPI) {
|
||||
this._ipfsAPI = ipfsAPI;
|
||||
}
|
||||
|
||||
on (type, callback) {
|
||||
return this.contract.on(type, callback);
|
||||
}
|
||||
|
@ -41,6 +41,8 @@ class Contribution extends Record {
|
||||
|
||||
async add (contributionAttr, callOptions = {}) {
|
||||
const contribution = new ContributionSerializer(contributionAttr);
|
||||
const confirmedAtBlock = contributionAttr.confirmedAtBlock || 0;
|
||||
const vetoed = contributionAttr.vetoed || false;
|
||||
|
||||
try { await contribution.validate(); }
|
||||
catch (error) { return Promise.reject(error); }
|
||||
@ -56,6 +58,8 @@ class Contribution extends Record {
|
||||
ipfsHashAttr.hashDigest,
|
||||
ipfsHashAttr.hashFunction,
|
||||
ipfsHashAttr.hashSize,
|
||||
confirmedAtBlock,
|
||||
vetoed,
|
||||
];
|
||||
|
||||
return this.contract.add(...contribution, callOptions);
|
||||
|
@ -52,6 +52,7 @@ class Contributor extends Record {
|
||||
|
||||
const jsonStr = contributor.serialize();
|
||||
|
||||
// console.log('Adding IPFS doc for', contributorAttr.account);
|
||||
return this.ipfs
|
||||
.add(jsonStr)
|
||||
.then((ipfsHashAttr) => {
|
||||
@ -62,7 +63,11 @@ class Contributor extends Record {
|
||||
ipfsHashAttr.hashSize,
|
||||
];
|
||||
|
||||
// console.log('Adding onchain record for', contributorAttr.account);
|
||||
return this.contract.addContributor(...contributor, callOptions);
|
||||
}).catch(err => {
|
||||
console.log('Failed to add IPFS document:', err.message);
|
||||
throw(err);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,5 @@ module.exports = {
|
||||
Contribution: require('./contribution'),
|
||||
Proposal: require('./proposal'),
|
||||
Token: require('./token'),
|
||||
Kernel: require('./kernel'),
|
||||
Acl: require('./acl'),
|
||||
Reimbursement: require('./reimbursement'),
|
||||
};
|
||||
|
@ -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;
|
71
lib/contracts/reimbursement.js
Normal file
71
lib/contracts/reimbursement.js
Normal file
@ -0,0 +1,71 @@
|
||||
const Record = require("./record");
|
||||
const ExpenseSerializer = require("../serializers/expense");
|
||||
|
||||
class Reimbursement extends Record {
|
||||
get count () {
|
||||
return this.contract.reimbursementsCount();
|
||||
}
|
||||
|
||||
getById (id) {
|
||||
return this.contract.get(id).then((data) => {
|
||||
return this.ipfs.catAndMerge(data, (ipfsDocument) => {
|
||||
const expenses = JSON.parse(ipfsDocument);
|
||||
return { expenses };
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getData (id) {
|
||||
return this.contract.get(id);
|
||||
}
|
||||
|
||||
async add (attrs, callOptions = {}) {
|
||||
const amount = parseInt(attrs.amount);
|
||||
const token = attrs.token;
|
||||
const recipientId = attrs.recipientId;
|
||||
const confirmedAtBlock = attrs.confirmedAtBlock || 0;
|
||||
const vetoed = attrs.vetoed || false;
|
||||
const expenses = attrs.expenses.map((e) => new ExpenseSerializer(e));
|
||||
let errorMessage;
|
||||
|
||||
if (typeof amount !== "number" || amount <= 0) {
|
||||
errorMessage = "Invalid data: amount must be a positive number.";
|
||||
}
|
||||
if (!token || token === "") {
|
||||
errorMessage = "Invalid data: token must be a token address.";
|
||||
}
|
||||
if (!recipientId || recipientId === "") {
|
||||
errorMessage = "Invalid data: recipientId is required.";
|
||||
}
|
||||
if (expenses.length === 0) {
|
||||
errorMessage = "Invalid data: at least one expense item is required.";
|
||||
}
|
||||
if (errorMessage) {
|
||||
return Promise.reject(new Error(errorMessage));
|
||||
}
|
||||
|
||||
return Promise.all(expenses.map((e) => e.validate())).then(() => {
|
||||
const jsonStr = JSON.stringify(
|
||||
expenses.map((e) => e.data),
|
||||
null,
|
||||
2
|
||||
);
|
||||
return this.ipfs.add(jsonStr).then((ipfsHashAttr) => {
|
||||
const reimbursement = [
|
||||
amount,
|
||||
token,
|
||||
parseInt(recipientId),
|
||||
ipfsHashAttr.hashDigest,
|
||||
ipfsHashAttr.hashFunction,
|
||||
ipfsHashAttr.hashSize,
|
||||
confirmedAtBlock,
|
||||
vetoed,
|
||||
];
|
||||
|
||||
return this.contract.add(...reimbursement, callOptions);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Reimbursement;
|
@ -6,19 +6,16 @@ const deprecate = require('./utils/deprecate');
|
||||
const ABIS = {
|
||||
Contributor: require('./abis/Contributor.json'),
|
||||
Contribution: require('./abis/Contribution.json'),
|
||||
Reimbursement: require('./abis/Reimbursement.json'),
|
||||
Token: require('./abis/Token.json'),
|
||||
Proposal: require('./abis/Proposal.json'),
|
||||
Kernel: require('./abis/Kernel.json'),
|
||||
Acl: require('./abis/ACL.json'),
|
||||
};
|
||||
const APP_CONTRACTS = [
|
||||
'Contributor',
|
||||
'Contribution',
|
||||
'Token',
|
||||
'Proposal',
|
||||
'Acl',
|
||||
];
|
||||
const DaoAddresses = require('./addresses/dao.json');
|
||||
// const APP_CONTRACTS = [
|
||||
// 'Contributor',
|
||||
// 'Contribution',
|
||||
// 'Token',
|
||||
// 'Reimbursement',
|
||||
// ];
|
||||
const Addresses = require('./addresses.json');
|
||||
|
||||
const Contracts = require('./contracts');
|
||||
const IPFS = require('./utils/ipfs');
|
||||
@ -32,7 +29,7 @@ function capitalize (word) {
|
||||
class Kredits {
|
||||
|
||||
constructor (provider, signer, options = {}) {
|
||||
let { addresses, abis, ipfsConfig } = options;
|
||||
const { addresses, abis, ipfsConfig } = options;
|
||||
|
||||
this.provider = provider;
|
||||
this.signer = signer;
|
||||
@ -43,21 +40,15 @@ class Kredits {
|
||||
this.contracts = {};
|
||||
}
|
||||
|
||||
init (names) {
|
||||
let contractsToLoad = names || APP_CONTRACTS;
|
||||
return this.provider.getNetwork().then(network => {
|
||||
this.addresses['Kernel'] = this.addresses['Kernel'] || DaoAddresses[network.chainId.toString()];
|
||||
let addressPromises = contractsToLoad.map((contractName) => {
|
||||
return this.Kernel.getApp(contractName).then((address) => {
|
||||
this.addresses[contractName] = address;
|
||||
}).catch((error) => {
|
||||
throw new Error(`Failed to get address for ${contractName} from DAO at ${this.Kernel.contract.address}
|
||||
- ${error.message}`
|
||||
);
|
||||
});
|
||||
});
|
||||
init (/* names */) {
|
||||
// TODO implement
|
||||
// const contractsToLoad = names || APP_CONTRACTS;
|
||||
|
||||
return Promise.all(addressPromises).then(() => { return this; });
|
||||
return this.provider.getNetwork().then(network => {
|
||||
if (Object.keys(this.addresses).length === 0) {
|
||||
this.addresses = Addresses[network.chainId.toString()];
|
||||
}
|
||||
return this;
|
||||
});
|
||||
}
|
||||
|
||||
@ -78,22 +69,16 @@ class Kredits {
|
||||
if (wallet) {
|
||||
signer = wallet.connect(ethProvider);
|
||||
} else if (ethProvider.getSigner) {
|
||||
signer = ethProvider.getSigner();
|
||||
// Only useful for reading data, not writing. The (unused) address is
|
||||
// necessary because without an address, ethers.js will try to look up
|
||||
// the provider's account 0, which doesn't work on our public RSK nodes.
|
||||
signer = ethProvider.getSigner('0xfa77675540E550b911a6AABF3805ac17C6641ec1');
|
||||
}
|
||||
return new Kredits(ethProvider, signer, kreditsOptions);
|
||||
}
|
||||
|
||||
static availableNetworks () {
|
||||
return Object.keys(DaoAddresses);
|
||||
}
|
||||
|
||||
get Kernel () {
|
||||
let k = this.contractFor('Kernel');
|
||||
// in case we want to use a special apm (e.g. development vs. production)
|
||||
if (this.options.apm) {
|
||||
k.apm = this.options.apm;
|
||||
}
|
||||
return k;
|
||||
return Object.keys(Addresses);
|
||||
}
|
||||
|
||||
get Contributor () {
|
||||
@ -102,11 +87,7 @@ class Kredits {
|
||||
|
||||
get Contributors () {
|
||||
deprecate('Contributors is deprecated use Contributor instead');
|
||||
return this.Contributor;
|
||||
}
|
||||
|
||||
get Proposal () {
|
||||
return this.contractFor('Proposal');
|
||||
return this.contractFor('Contributor');
|
||||
}
|
||||
|
||||
get Operator () {
|
||||
@ -121,8 +102,8 @@ class Kredits {
|
||||
return this.contractFor('Contribution');
|
||||
}
|
||||
|
||||
get Acl () {
|
||||
return this.contractFor('Acl');
|
||||
get Reimbursement () {
|
||||
return this.contractFor('Reimbursement');
|
||||
}
|
||||
|
||||
// Should be private
|
||||
@ -138,8 +119,8 @@ class Kredits {
|
||||
throw new Error(`Address or ABI not found for ${contractName}`);
|
||||
}
|
||||
|
||||
let signerOrProvider = this.signer || this.provider;
|
||||
let contract = new ethers.Contract(address, abi, signerOrProvider);
|
||||
const signerOrProvider = this.signer || this.provider;
|
||||
const contract = new ethers.Contract(address, abi, signerOrProvider);
|
||||
this.contracts[name] = new Contracts[contractName](contract);
|
||||
this.contracts[name].ipfs = this.ipfs;
|
||||
|
||||
|
@ -29,7 +29,7 @@ class KreditsKit {
|
||||
|
||||
appIdFor (contractName) {
|
||||
// see appIds in KreditsKit.sol for more details
|
||||
const knownContracts = ['Contribution', 'Contributor', 'Proposal', 'Token'];
|
||||
const knownContracts = ['Contribution', 'Contributor', 'Proposal', 'Reimbursement', 'Token'];
|
||||
return this.contract.appIds(knownContracts.indexOf(contractName));
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
const schemas = require('kosmos-schemas');
|
||||
const schemas = require('@kosmos/schemas');
|
||||
const validator = require('../utils/validator');
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,4 @@
|
||||
const schemas = require('kosmos-schemas');
|
||||
const schemas = require('@kosmos/schemas');
|
||||
const validator = require('../utils/validator');
|
||||
/**
|
||||
* Handle serialization for JSON-LD object of the contributor, according to
|
||||
|
104
lib/serializers/expense.js
Normal file
104
lib/serializers/expense.js
Normal file
@ -0,0 +1,104 @@
|
||||
const schemas = require('@kosmos/schemas');
|
||||
const validator = require('../utils/validator');
|
||||
|
||||
/**
|
||||
* Serialization and validation for JSON-LD document of the Expense
|
||||
*
|
||||
* @class
|
||||
* @public
|
||||
*/
|
||||
class ExpenseSerializer {
|
||||
|
||||
constructor (attrs) {
|
||||
Object.keys(attrs).forEach(a => this[a] = attrs[a]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize object to JSON
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
serialize () {
|
||||
// Write it pretty to ipfs
|
||||
return JSON.stringify(this.data, null, 2);
|
||||
}
|
||||
|
||||
get data () {
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
currency,
|
||||
amount,
|
||||
amountSats,
|
||||
date,
|
||||
url,
|
||||
tags,
|
||||
details,
|
||||
} = this;
|
||||
|
||||
const data = {
|
||||
'@context': 'https://schema.kosmos.org',
|
||||
'@type': 'Expense',
|
||||
title,
|
||||
description,
|
||||
currency,
|
||||
amount,
|
||||
amountSats,
|
||||
date,
|
||||
'tags': tags || [],
|
||||
'details': details || {},
|
||||
};
|
||||
|
||||
if (url) {
|
||||
data['url'] = url;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate serialized data against schema
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
validate () {
|
||||
const serialized = JSON.parse(this.serialize());
|
||||
const valid = validator.validate(serialized, schemas['expense']);
|
||||
return valid ? Promise.resolve() : Promise.reject(validator.error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize JSON to object
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
static deserialize (serialized) {
|
||||
const {
|
||||
title,
|
||||
description,
|
||||
currency,
|
||||
amount,
|
||||
amountSats,
|
||||
date,
|
||||
url,
|
||||
tags,
|
||||
details,
|
||||
} = JSON.parse(serialized.toString('utf8'));
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
currency,
|
||||
amount,
|
||||
amountSats,
|
||||
date,
|
||||
url,
|
||||
tags,
|
||||
details,
|
||||
ipfsData: serialized,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
module.exports = ExpenseSerializer;
|
@ -8,10 +8,17 @@ class IPFS {
|
||||
config = { host: 'localhost', port: '5001', protocol: 'http' };
|
||||
}
|
||||
this._config = config;
|
||||
this._ipfsAPI = ipfsClient(config);
|
||||
this._ipfsAPI = ipfsClient.create(config);
|
||||
|
||||
this._ipfsAPI.id().then(res => {
|
||||
console.debug('IPFS ID:', res.id);
|
||||
}).catch(e => {
|
||||
console.debug('IPFS config:', config);
|
||||
console.warn('Failed to initialize IPFS:', e.message);
|
||||
});
|
||||
}
|
||||
|
||||
catAndMerge (contractData, deserialize) {
|
||||
async catAndMerge (contractData, deserialize) {
|
||||
let data = {...contractData}; // data from ethers.js is not extensible. this copy the attributes in a new object
|
||||
// if no hash details are found simply return the data; nothing to merge
|
||||
if (!data.hashSize || data.hashSize === 0) {
|
||||
@ -22,20 +29,19 @@ class IPFS {
|
||||
|
||||
return this.cat(data.ipfsHash)
|
||||
.then(deserialize)
|
||||
.then((attributes) => {
|
||||
.then(attributes => {
|
||||
return Object.assign({}, data, attributes);
|
||||
});
|
||||
}
|
||||
|
||||
add (data) {
|
||||
return this._ipfsAPI
|
||||
.add(ipfsClient.Buffer.from(data))
|
||||
.then((res) => {
|
||||
return this.decodeHash(res[0].hash);
|
||||
async add (data) {
|
||||
return this._ipfsAPI.add(data)
|
||||
.then(res => {
|
||||
return this.decodeHash(res.path);
|
||||
});
|
||||
}
|
||||
|
||||
cat (hashData) {
|
||||
async cat (hashData) {
|
||||
let ipfsHash = hashData; // default - if it is a string
|
||||
if (Object.prototype.hasOwnProperty.call(hashData, 'hashSize')) {
|
||||
ipfsHash = this.encodeHash(hashData);
|
||||
@ -43,7 +49,12 @@ class IPFS {
|
||||
if (this._config['gatewayUrl']) {
|
||||
return fetch(`${this._config['gatewayUrl']}/${ipfsHash}`).then(r => r.text());
|
||||
} else {
|
||||
return this._ipfsAPI.cat(ipfsHash);
|
||||
const res = this._ipfsAPI.cat(ipfsHash);
|
||||
let str = '';
|
||||
for await (const buffer of res) {
|
||||
str += buffer.toString();
|
||||
}
|
||||
return Promise.resolve(str);
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,7 +77,7 @@ class IPFS {
|
||||
}
|
||||
|
||||
encodeHash (hashData) {
|
||||
let digest = ipfsClient.Buffer.from(hashData.hashDigest.slice(2), 'hex');
|
||||
const digest = Buffer.from(hashData.hashDigest.slice(2), 'hex');
|
||||
return multihashes.encode(digest, hashData.hashFunction, hashData.hashSize);
|
||||
}
|
||||
}
|
||||
|
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