Compare commits
58 Commits
v7.0.0-bet
...
v7.1.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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
|
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/
|
/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
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -5,6 +5,8 @@ node_modules
|
|||||||
yarn-error.log
|
yarn-error.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
data
|
||||||
|
|
||||||
cache
|
cache
|
||||||
artifacts
|
artifacts
|
||||||
.openzeppelin
|
.openzeppelin
|
||||||
|
|||||||
38
.travis.yml
38
.travis.yml
@@ -1,38 +0,0 @@
|
|||||||
---
|
|
||||||
language: node_js
|
|
||||||
node_js:
|
|
||||||
- "12"
|
|
||||||
|
|
||||||
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
|
||||||
24
README.md
24
README.md
@@ -1,4 +1,5 @@
|
|||||||
[](https://www.npmjs.com/package/kredits-contracts)
|
[](https://www.npmjs.com/package/@kredits/contracts)
|
||||||
|
[](https://drone.kosmos.org/kredits/contracts)
|
||||||
|
|
||||||
# Kredits Contracts
|
# Kredits Contracts
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ To run a local development chain run:
|
|||||||
|
|
||||||
### Bootstrap
|
### Bootstrap
|
||||||
|
|
||||||
1. Run an Ethereum node and ipfs
|
1. Run an EVM node and ipfs
|
||||||
|
|
||||||
$ npm run devchain
|
$ npm run devchain
|
||||||
$ ipfs daemon
|
$ ipfs daemon
|
||||||
@@ -56,6 +57,16 @@ If you need to fund development accounts with devchain coins:
|
|||||||
|
|
||||||
$ npm run fund # or hardhat fund --network localhost
|
$ 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
|
## Contract architecture
|
||||||
|
|
||||||
We use the [OpenZeppelin hardhat
|
We use the [OpenZeppelin hardhat
|
||||||
@@ -145,12 +156,3 @@ To run the console on one of the non localhost networks you can also just pass
|
|||||||
on the --network argument.
|
on the --network argument.
|
||||||
|
|
||||||
$ hardhat console --network rsk
|
$ hardhat console --network rsk
|
||||||
|
|
||||||
|
|
||||||
## Known Issues
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const contractCalls = [
|
|||||||
name: 'raucao',
|
name: 'raucao',
|
||||||
kind: 'person',
|
kind: 'person',
|
||||||
url: '',
|
url: '',
|
||||||
github_username: 'skddc',
|
github_username: 'raucao',
|
||||||
github_uid: 842,
|
github_uid: 842,
|
||||||
gitea_username: 'raucao',
|
gitea_username: 'raucao',
|
||||||
wiki_username: 'Basti',
|
wiki_username: 'Basti',
|
||||||
@@ -33,7 +33,7 @@ const contractCalls = [
|
|||||||
|
|
||||||
['Contribution', 'add', [{ contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF', date: '2019-04-11', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Test this thing', url: '' }, { gasLimit: 350000 }]],
|
['Contribution', 'add', [{ contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF', date: '2019-04-11', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Test this thing', 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', 'add', [{ contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-11', amount: 1500, kind: 'dev', description: '[67P/kredits-web] Reviewed stuff', url: '' }, { gasLimit: 350000 }]],
|
||||||
['Contribution', 'add', [{ contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF', date: '2019-04-11', amount: 1500, kind: 'dev', description: '[67P/kredits-contracts] Add tests', url: '' }, { gasLimit: 350000 }]],
|
['Contribution', 'add', [{ contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF', date: '2019-04-11', amount: 5000, kind: 'dev', description: '[67P/kredits-contracts] Add tests', url: '' }, { 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: 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: 5000, kind: 'dev', description: '[67P/kredits-web] Expense UI, first draft', url: '' }, { gasLimit: 350000 }]],
|
['Contribution', 'add', [{ contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-11', amount: 5000, kind: 'dev', description: '[67P/kredits-web] Expense UI, first draft', url: '' }, { gasLimit: 350000 }]],
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,6 @@ pragma solidity ^0.8.0;
|
|||||||
|
|
||||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||||
|
|
||||||
interface IToken {
|
|
||||||
function mintFor(address contributorAccount, uint256 amount, uint32 contributionId) external;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface ContributorInterface {
|
interface ContributorInterface {
|
||||||
function getContributorAddressById(uint32 contributorId) external view returns (address);
|
function getContributorAddressById(uint32 contributorId) external view returns (address);
|
||||||
function getContributorIdByAddress(address contributorAccount) external view returns (uint32);
|
function getContributorIdByAddress(address contributorAccount) external view returns (uint32);
|
||||||
@@ -16,12 +12,10 @@ interface ContributorInterface {
|
|||||||
|
|
||||||
contract Contribution is Initializable {
|
contract Contribution is Initializable {
|
||||||
ContributorInterface public contributorContract;
|
ContributorInterface public contributorContract;
|
||||||
IToken public tokenContract;
|
|
||||||
|
|
||||||
struct ContributionData {
|
struct ContributionData {
|
||||||
uint32 contributorId;
|
uint32 contributorId;
|
||||||
uint32 amount;
|
uint32 amount;
|
||||||
bool claimed;
|
|
||||||
bytes32 hashDigest;
|
bytes32 hashDigest;
|
||||||
uint8 hashFunction;
|
uint8 hashFunction;
|
||||||
uint8 hashSize;
|
uint8 hashSize;
|
||||||
@@ -42,10 +36,16 @@ contract Contribution is Initializable {
|
|||||||
mapping(uint32 => ContributionData) public contributions;
|
mapping(uint32 => ContributionData) public contributions;
|
||||||
uint32 public contributionsCount;
|
uint32 public contributionsCount;
|
||||||
|
|
||||||
|
// Confirmation veto period
|
||||||
uint32 public blocksToWait;
|
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 ContributionAdded(uint32 id, uint32 indexed contributorId, uint32 amount);
|
||||||
event ContributionClaimed(uint32 id, uint32 indexed contributorId, uint32 amount);
|
|
||||||
event ContributionVetoed(uint32 id, address vetoedByAccount);
|
event ContributionVetoed(uint32 id, address vetoedByAccount);
|
||||||
|
|
||||||
modifier onlyCore {
|
modifier onlyCore {
|
||||||
@@ -53,13 +53,19 @@ contract Contribution is Initializable {
|
|||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
modifier onlyDeployer {
|
||||||
|
require(msg.sender == deployer, "Deployer only");
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
function initialize(uint32 blocksToWait_) public initializer {
|
function initialize(uint32 blocksToWait_) public initializer {
|
||||||
|
deployer = msg.sender;
|
||||||
|
migrationDone = false;
|
||||||
blocksToWait = blocksToWait_;
|
blocksToWait = blocksToWait_;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setTokenContract(address token) public {
|
function finishMigration() public onlyDeployer {
|
||||||
require(address(tokenContract) == address(0) || contributorContract.addressIsCore(msg.sender), "Core only");
|
migrationDone = true;
|
||||||
tokenContract = IToken(token);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setContributorContract(address contributor) public {
|
function setContributorContract(address contributor) public {
|
||||||
@@ -133,14 +139,13 @@ contract Contribution is Initializable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
id = contributionId;
|
||||||
ContributionData storage c = contributions[id];
|
ContributionData storage c = contributions[id];
|
||||||
return (
|
return (
|
||||||
id,
|
id,
|
||||||
c.contributorId,
|
c.contributorId,
|
||||||
c.amount,
|
c.amount,
|
||||||
c.claimed,
|
|
||||||
c.hashDigest,
|
c.hashDigest,
|
||||||
c.hashFunction,
|
c.hashFunction,
|
||||||
c.hashSize,
|
c.hashSize,
|
||||||
@@ -150,24 +155,29 @@ contract Contribution is Initializable {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function add(uint32 amount, uint32 contributorId, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public {
|
function add(uint32 amount, uint32 contributorId, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, uint256 confirmedAtBlock, bool vetoed) public {
|
||||||
// require(canPerform(msg.sender, ADD_CONTRIBUTION_ROLE, new uint32[](0)), 'nope');
|
// require(canPerform(msg.sender, ADD_CONTRIBUTION_ROLE, new uint32[](0)), 'nope');
|
||||||
require(balanceOf(msg.sender) > 0 || contributorContract.addressIsCore(msg.sender), 'must have kredits or core');
|
// TODO hubot neither has kredits nor a core account
|
||||||
|
require((confirmedAtBlock == 0 && vetoed == false) || migrationDone == false, 'extra arguments during migration only');
|
||||||
|
require(balanceOf(msg.sender) > 0 || contributorContract.addressIsCore(msg.sender), 'requires kredits or core status');
|
||||||
|
|
||||||
uint32 contributionId = contributionsCount + 1;
|
uint32 contributionId = contributionsCount + 1;
|
||||||
ContributionData storage c = contributions[contributionId];
|
ContributionData storage c = contributions[contributionId];
|
||||||
c.exists = true;
|
c.exists = true;
|
||||||
c.amount = amount;
|
c.amount = amount;
|
||||||
c.claimed = false;
|
|
||||||
c.contributorId = contributorId;
|
c.contributorId = contributorId;
|
||||||
c.hashDigest = hashDigest;
|
c.hashDigest = hashDigest;
|
||||||
c.hashFunction = hashFunction;
|
c.hashFunction = hashFunction;
|
||||||
c.hashSize = hashSize;
|
c.hashSize = hashSize;
|
||||||
if (contributionId < 10) {
|
|
||||||
c.confirmedAtBlock = block.number;
|
if (confirmedAtBlock > 0) {
|
||||||
|
c.confirmedAtBlock = confirmedAtBlock;
|
||||||
} else {
|
} else {
|
||||||
c.confirmedAtBlock = block.number + 1 + blocksToWait;
|
c.confirmedAtBlock = block.number + 1 + blocksToWait;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (vetoed) { c.vetoed = true; }
|
||||||
|
|
||||||
contributionsCount++;
|
contributionsCount++;
|
||||||
|
|
||||||
contributionOwner[contributionId] = contributorId;
|
contributionOwner[contributionId] = contributorId;
|
||||||
@@ -177,32 +187,15 @@ contract Contribution is Initializable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function veto(uint32 contributionId) public onlyCore {
|
function veto(uint32 contributionId) public onlyCore {
|
||||||
|
|
||||||
ContributionData storage c = contributions[contributionId];
|
ContributionData storage c = contributions[contributionId];
|
||||||
require(c.exists, 'NOT_FOUND');
|
require(c.exists, 'NOT_FOUND');
|
||||||
require(!c.claimed, 'ALREADY_CLAIMED');
|
|
||||||
require(block.number < c.confirmedAtBlock, 'VETO_PERIOD_ENDED');
|
require(block.number < c.confirmedAtBlock, 'VETO_PERIOD_ENDED');
|
||||||
c.vetoed = true;
|
c.vetoed = true;
|
||||||
|
|
||||||
emit ContributionVetoed(contributionId, msg.sender);
|
emit ContributionVetoed(contributionId, msg.sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
function claim(uint32 contributionId) public {
|
|
||||||
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 contributorAccount = getContributorAddressById(c.contributorId);
|
|
||||||
uint256 amount = uint256(c.amount);
|
|
||||||
tokenContract.mintFor(contributorAccount, amount, contributionId);
|
|
||||||
emit ContributionClaimed(contributionId, c.contributorId, c.amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
function exists(uint32 contributionId) public view returns (bool) {
|
function exists(uint32 contributionId) public view returns (bool) {
|
||||||
return contributions[contributionId].exists;
|
return contributions[contributionId].exists;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ pragma solidity ^0.8.0;
|
|||||||
|
|
||||||
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
|
||||||
|
|
||||||
interface ITokenBalance {
|
interface IToken {
|
||||||
|
function mintFor(address contributorAccount, uint256 amount) external;
|
||||||
function balanceOf(address contributorAccount) external view returns (uint256);
|
function balanceOf(address contributorAccount) external view returns (uint256);
|
||||||
}
|
}
|
||||||
interface IContributionBalance {
|
interface IContributionBalance {
|
||||||
@@ -11,9 +12,10 @@ interface IContributionBalance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
contract Contributor is Initializable {
|
contract Contributor is Initializable {
|
||||||
address deployer;
|
address public deployer;
|
||||||
|
address public profileManager;
|
||||||
IContributionBalance public contributionContract;
|
IContributionBalance public contributionContract;
|
||||||
ITokenBalance public tokenContract;
|
IToken public tokenContract;
|
||||||
|
|
||||||
struct Contributor {
|
struct Contributor {
|
||||||
address account;
|
address account;
|
||||||
@@ -21,6 +23,7 @@ contract Contributor is Initializable {
|
|||||||
uint8 hashFunction;
|
uint8 hashFunction;
|
||||||
uint8 hashSize;
|
uint8 hashSize;
|
||||||
bool exists;
|
bool exists;
|
||||||
|
uint32 kreditsWithdrawn;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapping (address => uint32) public contributorIds;
|
mapping (address => uint32) public contributorIds;
|
||||||
@@ -36,8 +39,14 @@ contract Contributor is Initializable {
|
|||||||
_;
|
_;
|
||||||
}
|
}
|
||||||
|
|
||||||
function initialize() public initializer {
|
modifier onlyContributors {
|
||||||
|
require(addressExists(msg.sender) && contributionContract.balanceOf(msg.sender) > 0, "Contributors only");
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initialize(address profileManagerAddress) public initializer {
|
||||||
deployer = msg.sender;
|
deployer = msg.sender;
|
||||||
|
profileManager = profileManagerAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
function setContributionContract(address contribution) public onlyCore {
|
function setContributionContract(address contribution) public onlyCore {
|
||||||
@@ -47,7 +56,7 @@ contract Contributor is Initializable {
|
|||||||
|
|
||||||
function setTokenContract(address token) public onlyCore {
|
function setTokenContract(address token) public onlyCore {
|
||||||
require(address(tokenContract) == address(0) || addressIsCore(msg.sender), "Core only");
|
require(address(tokenContract) == address(0) || addressIsCore(msg.sender), "Core only");
|
||||||
tokenContract = ITokenBalance(token);
|
tokenContract = IToken(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
function coreContributorsCount() public view returns (uint32) {
|
function coreContributorsCount() public view returns (uint32) {
|
||||||
@@ -77,11 +86,12 @@ contract Contributor is Initializable {
|
|||||||
c.hashFunction = hashFunction;
|
c.hashFunction = hashFunction;
|
||||||
c.hashSize = hashSize;
|
c.hashSize = hashSize;
|
||||||
|
|
||||||
ContributorProfileUpdated(id, oldHashDigest, c.hashDigest);
|
emit ContributorProfileUpdated(id, oldHashDigest, c.hashDigest);
|
||||||
}
|
}
|
||||||
|
|
||||||
function addContributor(address account, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public onlyCore {
|
function addContributor(address account, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public {
|
||||||
require(!addressExists(account));
|
require(!addressExists(account), "Address already in use");
|
||||||
|
require((msg.sender == profileManager) || addressIsCore(msg.sender), "Only core and profile manager");
|
||||||
uint32 _id = contributorsCount + 1;
|
uint32 _id = contributorsCount + 1;
|
||||||
assert(!contributors[_id].exists); // this can not be acually
|
assert(!contributors[_id].exists); // this can not be acually
|
||||||
Contributor storage c = contributors[_id];
|
Contributor storage c = contributors[_id];
|
||||||
@@ -90,6 +100,7 @@ contract Contributor is Initializable {
|
|||||||
c.hashFunction = hashFunction;
|
c.hashFunction = hashFunction;
|
||||||
c.hashSize = hashSize;
|
c.hashSize = hashSize;
|
||||||
c.account = account;
|
c.account = account;
|
||||||
|
c.kreditsWithdrawn = 0;
|
||||||
contributorIds[account] = _id;
|
contributorIds[account] = _id;
|
||||||
|
|
||||||
contributorsCount += 1;
|
contributorsCount += 1;
|
||||||
@@ -132,7 +143,7 @@ contract Contributor is Initializable {
|
|||||||
return contributors[id];
|
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 ) {
|
function getContributorById(uint32 _id) view public 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;
|
id = _id;
|
||||||
Contributor storage c = contributors[_id];
|
Contributor storage c = contributors[_id];
|
||||||
account = c.account;
|
account = c.account;
|
||||||
@@ -144,6 +155,19 @@ contract Contributor is Initializable {
|
|||||||
totalKreditsEarned = contributionContract.totalKreditsEarnedByContributor(_id, true);
|
totalKreditsEarned = contributionContract.totalKreditsEarnedByContributor(_id, true);
|
||||||
contributionsCount = contributionContract.balanceOf(c.account);
|
contributionsCount = contributionContract.balanceOf(c.account);
|
||||||
exists = c.exists;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ contract Reimbursement is Initializable {
|
|||||||
struct ReimbursementData {
|
struct ReimbursementData {
|
||||||
uint32 recipientId;
|
uint32 recipientId;
|
||||||
uint256 amount;
|
uint256 amount;
|
||||||
|
// TODO remove token entirely
|
||||||
address token;
|
address token;
|
||||||
bytes32 hashDigest;
|
bytes32 hashDigest;
|
||||||
uint8 hashFunction;
|
uint8 hashFunction;
|
||||||
|
|||||||
@@ -7,38 +7,35 @@ interface ContributorInterface {
|
|||||||
function getContributorAddressById(uint32 contributorId) external view returns (address);
|
function getContributorAddressById(uint32 contributorId) external view returns (address);
|
||||||
function getContributorIdByAddress(address contributorAccount) external view returns (uint32);
|
function getContributorIdByAddress(address contributorAccount) external view returns (uint32);
|
||||||
function addressIsCore(address sender) external view returns (bool);
|
function addressIsCore(address sender) external view returns (bool);
|
||||||
// TODO Maybe use for validation
|
|
||||||
// function exists(uint32 contributorId) public view returns (bool);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
contract Token is Initializable, ERC20Upgradeable {
|
contract Token is Initializable, ERC20Upgradeable {
|
||||||
ContributorInterface public contributorContract;
|
ContributorInterface public contributorContract;
|
||||||
using SafeMathUpgradeable for uint256;
|
using SafeMathUpgradeable for uint256;
|
||||||
|
|
||||||
address public contributionContract;
|
address public contributorContractAddress;
|
||||||
|
|
||||||
event LogMint(address indexed recipient, uint256 amount, uint32 contributionId);
|
event KreditsMinted(address indexed recipient, uint256 amount);
|
||||||
|
|
||||||
function initialize() public virtual initializer {
|
function initialize() public virtual initializer {
|
||||||
__ERC20_init('Kredits', 'KS');
|
__ERC20_init("Kredits", "KS");
|
||||||
}
|
}
|
||||||
|
|
||||||
function setContributionContract(address contribution) public {
|
function decimals() public view virtual override returns (uint8) {
|
||||||
require(address(contributionContract) == address(0) || contributorContract.addressIsCore(msg.sender), "Core only");
|
return 0;
|
||||||
contributionContract = contribution;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setContributorContract(address contributor) public {
|
function setContributorContract(address contributor) public {
|
||||||
require(address(contributorContract) == address(0) || contributorContract.addressIsCore(msg.sender), "Core only");
|
require(address(contributorContract) == address(0) || contributorContract.addressIsCore(msg.sender), "Core only");
|
||||||
contributorContract = ContributorInterface(contributor);
|
contributorContract = ContributorInterface(contributor);
|
||||||
|
contributorContractAddress = contributor;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mintFor(address contributorAccount, uint256 amount, uint32 contributionId) public {
|
function mintFor(address contributorAccount, uint256 amount) public {
|
||||||
require(contributionContract == msg.sender, "Only Contribution");
|
require(contributorContractAddress == msg.sender, "Only Contributor");
|
||||||
require(amount > 0, "INVALID_AMOUNT");
|
require(amount > 0, "INVALID_AMOUNT");
|
||||||
|
|
||||||
uint256 amountInWei = amount.mul(1 ether);
|
_mint(contributorAccount, amount);
|
||||||
_mint(contributorAccount, amountInWei);
|
emit KreditsMinted(contributorAccount, amount);
|
||||||
emit LogMint(contributorAccount, amount, contributionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
require("@nomiclabs/hardhat-waffle");
|
require("@nomiclabs/hardhat-waffle");
|
||||||
require("hardhat-deploy");
|
require("hardhat-deploy");
|
||||||
require("hardhat-deploy-ethers");
|
require("hardhat-deploy-ethers");
|
||||||
|
require("@nomicfoundation/hardhat-chai-matchers");
|
||||||
require("@openzeppelin/hardhat-upgrades");
|
require("@openzeppelin/hardhat-upgrades");
|
||||||
const Kredits = require("./lib/kredits");
|
const Kredits = require("./lib/kredits");
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
|||||||
[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint32","name":"contributionId","type":"uint32"}],"name":"LogMint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"contributionContract","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"contributorContract","outputs":[{"internalType":"contract ContributorInterface","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contributorAccount","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint32","name":"contributionId","type":"uint32"}],"name":"mintFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contribution","type":"address"}],"name":"setContributionContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contributor","type":"address"}],"name":"setContributorContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]
|
[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"KreditsMinted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"contributorContract","outputs":[{"internalType":"contract ContributorInterface","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"contributorContractAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"contributorAccount","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"mintFor","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"contributor","type":"address"}],"name":"setContributorContract","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
{
|
{
|
||||||
|
"31": {
|
||||||
|
"Contributor": "0xf1073Dab6e305583F95e451Cba449bB867a6e3Fd",
|
||||||
|
"Contribution": "0x1C531F824e339cD37D75B7F391cB8E42e0E0d4bd",
|
||||||
|
"Token": "0x56F64C3BB45e6a248F4C783f5a1633E53D6A2371",
|
||||||
|
"Reimbursement": "0x9C5fFBFba2570A9b31D60338453C5480Ce74B342"
|
||||||
|
},
|
||||||
"1337": {
|
"1337": {
|
||||||
"Contributor": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0",
|
"Contributor": "0xCc66f9A3cA2670972938FAD91d0865c4a62DFB25",
|
||||||
"Contribution": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9",
|
"Contribution": "0x8999CaBc43E28202c5A2257f2a95A45b1F8A62BD",
|
||||||
"Token": "0x0165878A594ca255338adfa4d48449f69242Eb8F",
|
"Token": "0xe082678eCF749982e33Ea6839852a8cd989aEDE2",
|
||||||
"Reimbursement": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6"
|
"Reimbursement": "0x984f797d26d3da2E9b9f8Ae4eeFEACC60fCAA90C"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -58,7 +58,7 @@ class Contribution extends Record {
|
|||||||
ipfsHashAttr.hashSize,
|
ipfsHashAttr.hashSize,
|
||||||
];
|
];
|
||||||
|
|
||||||
return this.contract.add(...contribution, callOptions);
|
return this.contract.add(...contribution, 0, false, callOptions);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class Reimbursement extends Record {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getData (id) {
|
getData (id) {
|
||||||
return this.contract.getReimbursement(id);
|
return this.contract.get(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
async add (attrs, callOptions = {}) {
|
async add (attrs, callOptions = {}) {
|
||||||
|
|||||||
@@ -69,7 +69,10 @@ class Kredits {
|
|||||||
if (wallet) {
|
if (wallet) {
|
||||||
signer = wallet.connect(ethProvider);
|
signer = wallet.connect(ethProvider);
|
||||||
} else if (ethProvider.getSigner) {
|
} 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);
|
return new Kredits(ethProvider, signer, kreditsOptions);
|
||||||
}
|
}
|
||||||
|
|||||||
878
package-lock.json
generated
878
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
11
package.json
11
package.json
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "kredits-contracts",
|
"name": "@kredits/contracts",
|
||||||
"version": "7.0.0-beta.0",
|
"version": "7.1.0",
|
||||||
"description": "Ethereum contracts and npm wrapper for Kredits",
|
"description": "Smart contracts and JavaScript API for Kredits",
|
||||||
"main": "./lib/kredits.js",
|
"main": "./lib/kredits.js",
|
||||||
"directories": {
|
"directories": {
|
||||||
"test": "test"
|
"test": "test"
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
"lint:contracts": "solhint \"contracts/**/*.sol\" \"apps/*/contracts/**/*.sol\"",
|
"lint:contracts": "solhint \"contracts/**/*.sol\" \"apps/*/contracts/**/*.sol\"",
|
||||||
"lint:contract-tests": "eslint apps/*/test",
|
"lint:contract-tests": "eslint apps/*/test",
|
||||||
"lint:wrapper": "eslint lib/",
|
"lint:wrapper": "eslint lib/",
|
||||||
"test": "npm run test:token && npm run test:contributor && npm run test:contribution && npm run test:proposal",
|
"test": "hardhat test",
|
||||||
"test:token": "cd apps/token && npm run test",
|
"test:token": "cd apps/token && npm run test",
|
||||||
"test:contributor": "cd apps/contributor && npm run test",
|
"test:contributor": "cd apps/contributor && npm run test",
|
||||||
"test:contribution": "cd apps/contribution && npm run test",
|
"test:contribution": "cd apps/contribution && npm run test",
|
||||||
@@ -39,11 +39,13 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/67P/kredits-contracts#readme",
|
"homepage": "https://github.com/67P/kredits-contracts#readme",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@nomicfoundation/hardhat-chai-matchers": "^1.0.3",
|
||||||
"@nomiclabs/hardhat-ethers": "^2.0.2",
|
"@nomiclabs/hardhat-ethers": "^2.0.2",
|
||||||
"@nomiclabs/hardhat-waffle": "^2.0.1",
|
"@nomiclabs/hardhat-waffle": "^2.0.1",
|
||||||
"@openzeppelin/contracts-upgradeable": "^4.3.2",
|
"@openzeppelin/contracts-upgradeable": "^4.3.2",
|
||||||
"@openzeppelin/hardhat-upgrades": "^1.10.0",
|
"@openzeppelin/hardhat-upgrades": "^1.10.0",
|
||||||
"async-each-series": "^1.1.0",
|
"async-each-series": "^1.1.0",
|
||||||
|
"chai": "^4.3.6",
|
||||||
"cli-table": "^0.3.1",
|
"cli-table": "^0.3.1",
|
||||||
"colors": "^1.0.3",
|
"colors": "^1.0.3",
|
||||||
"eslint": "^8.14.0",
|
"eslint": "^8.14.0",
|
||||||
@@ -57,6 +59,7 @@
|
|||||||
"hardhat-deploy": "^0.11.4",
|
"hardhat-deploy": "^0.11.4",
|
||||||
"hardhat-deploy-ethers": "^0.3.0-beta.10",
|
"hardhat-deploy-ethers": "^0.3.0-beta.10",
|
||||||
"homedir": "^0.6.0",
|
"homedir": "^0.6.0",
|
||||||
|
"mocha": "^10.0.0",
|
||||||
"promptly": "^3.0.3",
|
"promptly": "^3.0.3",
|
||||||
"solhint": "^3.3.7",
|
"solhint": "^3.3.7",
|
||||||
"truffle-hdwallet-provider": "^1.0.17",
|
"truffle-hdwallet-provider": "^1.0.17",
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
const { ethers, upgrades } = require("hardhat"); const path = require("path"); const fileInject = require("./helpers/file_inject.js");
|
const { ethers, upgrades } = require("hardhat");
|
||||||
|
const path = require("path");
|
||||||
|
const fileInject = require("./helpers/file_inject.js");
|
||||||
|
|
||||||
function handleError(error) {
|
function handleError(error) {
|
||||||
console.error(error.message);
|
console.error(error.message);
|
||||||
@@ -34,7 +36,7 @@ async function main() {
|
|||||||
|
|
||||||
const blocksVetoPeriod = 40320; // 7 days; 15 seconds block time
|
const blocksVetoPeriod = 40320; // 7 days; 15 seconds block time
|
||||||
|
|
||||||
await deployContractProxy('Contributor');
|
await deployContractProxy('Contributor', [ '0x0000000000000000000000000000000000000000' ] );
|
||||||
await deployContractProxy('Contribution', [ blocksVetoPeriod ]);
|
await deployContractProxy('Contribution', [ blocksVetoPeriod ]);
|
||||||
await deployContractProxy('Token');
|
await deployContractProxy('Token');
|
||||||
await deployContractProxy('Reimbursement');
|
await deployContractProxy('Reimbursement');
|
||||||
@@ -55,16 +57,6 @@ async function main() {
|
|||||||
return res.wait();
|
return res.wait();
|
||||||
}).catch(handleError);
|
}).catch(handleError);
|
||||||
|
|
||||||
|
|
||||||
console.log('Calling Contribution#setTokenContract')
|
|
||||||
await contracts.Contribution.functions
|
|
||||||
.setTokenContract(contracts.Token.address)
|
|
||||||
.then(res => {
|
|
||||||
console.log(`...transaction published: ${res.hash}`);
|
|
||||||
return res.wait();
|
|
||||||
}).catch(handleError);
|
|
||||||
|
|
||||||
|
|
||||||
console.log('Calling Contribution#setContributorContract')
|
console.log('Calling Contribution#setContributorContract')
|
||||||
await contracts.Contribution.functions
|
await contracts.Contribution.functions
|
||||||
.setContributorContract(contracts.Contributor.address)
|
.setContributorContract(contracts.Contributor.address)
|
||||||
@@ -73,14 +65,6 @@ async function main() {
|
|||||||
return res.wait();
|
return res.wait();
|
||||||
}).catch(handleError);
|
}).catch(handleError);
|
||||||
|
|
||||||
console.log('Calling Token#setContributionContract')
|
|
||||||
await contracts.Token.functions
|
|
||||||
.setContributionContract(contracts.Contribution.address)
|
|
||||||
.then(res => {
|
|
||||||
console.log(`...transaction published: ${res.hash}`);
|
|
||||||
return res.wait();
|
|
||||||
}).catch(handleError);
|
|
||||||
|
|
||||||
console.log('Calling Token#setContributorContract')
|
console.log('Calling Token#setContributorContract')
|
||||||
await contracts.Token.functions
|
await contracts.Token.functions
|
||||||
.setContributorContract(contracts.Contributor.address)
|
.setContributorContract(contracts.Contributor.address)
|
||||||
|
|||||||
42
scripts/export/contributions.js
Normal file
42
scripts/export/contributions.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
const Kredits = require('../../lib/kredits');
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
kredits = new Kredits(hre.ethers.provider, hre.ethers.provider.getSigner())
|
||||||
|
await kredits.init();
|
||||||
|
|
||||||
|
console.log(`Using Contribution at: ${kredits.Contribution.contract.address}`);
|
||||||
|
|
||||||
|
const count = await kredits.Contribution.count;
|
||||||
|
const currentBlockHeight = await hre.ethers.provider.getBlockNumber();
|
||||||
|
|
||||||
|
const backup = {};
|
||||||
|
const promises = [];
|
||||||
|
for (let i = 1; i <= count; i++) {
|
||||||
|
promises.push(new Promise((resolve, reject) => {
|
||||||
|
setTimeout(async () => {
|
||||||
|
console.log(`Loading contribution #${i}`);
|
||||||
|
await kredits.Contribution.contract.getContribution(i).then(contractData => {
|
||||||
|
backup[i] = {
|
||||||
|
amount: contractData.amount,
|
||||||
|
contributorId: contractData.contributorId,
|
||||||
|
hashDigest: contractData.hashDigest,
|
||||||
|
hashFunction: contractData.hashFunction,
|
||||||
|
hashSize: contractData.hashSize,
|
||||||
|
confirmedAtBlock: contractData.confirmedAtBlock,
|
||||||
|
confirmed: contractData.confirmedAtBlock <= currentBlockHeight,
|
||||||
|
vetoed: contractData.vetoed,
|
||||||
|
id: contractData.id,
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}, 100 * i);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises).then(() => {
|
||||||
|
fs.writeFileSync("./data/contributions.json", JSON.stringify(backup, null, 2));
|
||||||
|
console.log("Exported");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
38
scripts/export/contributors.js
Normal file
38
scripts/export/contributors.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const Kredits = require('../../lib/kredits');
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
kredits = new Kredits(hre.ethers.provider, hre.ethers.provider.getSigner())
|
||||||
|
await kredits.init();
|
||||||
|
|
||||||
|
console.log(`Using Contributor at: ${kredits.Contributor.contract.address}`);
|
||||||
|
|
||||||
|
const count = await kredits.Contributor.count;
|
||||||
|
|
||||||
|
const backup = {};
|
||||||
|
const promises = [];
|
||||||
|
for (let i = 1; i <= count; i++) {
|
||||||
|
promises.push(new Promise((resolve, reject) => {
|
||||||
|
setTimeout(async () => {
|
||||||
|
console.log(`Loading contributor #${i}`);
|
||||||
|
await kredits.Contributor.contract.getContributorById(i).then(contractData => {
|
||||||
|
backup[i] = {
|
||||||
|
account: contractData.account,
|
||||||
|
hashDigest: contractData.hashDigest,
|
||||||
|
hashFunction: contractData.hashFunction,
|
||||||
|
hashSize: contractData.hashSize,
|
||||||
|
id: contractData.id,
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
}, 100 * i);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises).then(() => {
|
||||||
|
fs.writeFileSync("./data/contributors.json", JSON.stringify(backup, null, 2));
|
||||||
|
console.log("Exported");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
41
scripts/import/contributions.js
Normal file
41
scripts/import/contributions.js
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const Kredits = require('../../lib/kredits');
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
kredits = new Kredits(hre.ethers.provider, hre.ethers.provider.getSigner())
|
||||||
|
await kredits.init();
|
||||||
|
|
||||||
|
console.log(`Using Contribution at: ${kredits.Contribution.contract.address}`);
|
||||||
|
const count = await kredits.Contribution.count;
|
||||||
|
console.log(`Currently ${count} entries`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = fs.readFileSync("./data/contributions.json");
|
||||||
|
const contributions = JSON.parse(data);
|
||||||
|
const ids = Object.keys(contributions)
|
||||||
|
.map(k => parseInt(k))
|
||||||
|
.sort(function(a, b){return a-b});
|
||||||
|
|
||||||
|
const currentBlockHeight = await kredits.provider.getBlockNumber();
|
||||||
|
const confirmationPeriod = 40320 // blocks
|
||||||
|
const unconfirmedHeight = currentBlockHeight + confirmationPeriod;
|
||||||
|
|
||||||
|
for (const contributionId of ids) {
|
||||||
|
const c = contributions[contributionId.toString()];
|
||||||
|
|
||||||
|
const confirmedAtBlock = c.confirmed ? currentBlockHeight : unconfirmedHeight;
|
||||||
|
|
||||||
|
const result = await kredits.Contribution.contract.add(
|
||||||
|
c.amount, c.contributorId,
|
||||||
|
c.hashDigest, c.hashFunction, c.hashSize,
|
||||||
|
confirmedAtBlock, c.vetoed
|
||||||
|
);
|
||||||
|
console.log(`Adding contribution #${contributionId}: ${result.hash}`);
|
||||||
|
await result.wait();
|
||||||
|
};
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
34
scripts/import/contributors.js
Normal file
34
scripts/import/contributors.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const Kredits = require('../../lib/kredits');
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
kredits = new Kredits(hre.ethers.provider, hre.ethers.provider.getSigner())
|
||||||
|
await kredits.init();
|
||||||
|
|
||||||
|
console.log(`Using Contributor at: ${kredits.Contributor.contract.address}`);
|
||||||
|
const count = await kredits.Contributor.count;
|
||||||
|
console.log(`Currently ${count} entries`);
|
||||||
|
try {
|
||||||
|
const data = fs.readFileSync("./data/contributors.json");
|
||||||
|
const contributors = JSON.parse(data);
|
||||||
|
const ids = Object.keys(contributors)
|
||||||
|
.map(k => parseInt(k))
|
||||||
|
.sort(function(a, b){return a-b});
|
||||||
|
|
||||||
|
for (const contributorId of ids) {
|
||||||
|
const contributor = contributors[contributorId.toString()];
|
||||||
|
const result = await kredits.Contributor.contract.addContributor(
|
||||||
|
contributor.account,
|
||||||
|
contributor.hashDigest,
|
||||||
|
contributor.hashFunction,
|
||||||
|
contributor.hashSize,
|
||||||
|
);
|
||||||
|
console.log(`Adding contributor #${contributorId}: ${result.hash}`);
|
||||||
|
await result.wait();
|
||||||
|
};
|
||||||
|
} catch(e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
@@ -12,7 +12,7 @@ async function main() {
|
|||||||
console.log(`Using Contribution at: ${kredits.Contribution.contract.address}`);
|
console.log(`Using Contribution at: ${kredits.Contribution.contract.address}`);
|
||||||
|
|
||||||
const table = new Table({
|
const table = new Table({
|
||||||
head: ['ID', 'Contributor ID', 'Description', 'Amount', 'Confirmed?', 'Vetoed?', 'Claimed?', 'IPFS']
|
head: ['ID', 'Contributor ID', 'Description', 'Amount', 'Confirmed?', 'Vetoed?', 'IPFS']
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -31,7 +31,6 @@ async function main() {
|
|||||||
c.amount.toString(),
|
c.amount.toString(),
|
||||||
`${confirmed} (${c.confirmedAtBlock})`,
|
`${confirmed} (${c.confirmedAtBlock})`,
|
||||||
c.vetoed,
|
c.vetoed,
|
||||||
c.claimed,
|
|
||||||
c.ipfsHash
|
c.ipfsHash
|
||||||
])
|
])
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ async function main() {
|
|||||||
await kredits.init();
|
await kredits.init();
|
||||||
|
|
||||||
console.log(`Using Contributor at: ${kredits.Contributor.contract.address}`);
|
console.log(`Using Contributor at: ${kredits.Contributor.contract.address}`);
|
||||||
|
const count = await kredits.Contributors.count;
|
||||||
|
console.log(`Currently ${count} entries`);
|
||||||
|
|
||||||
const table = new Table({
|
const table = new Table({
|
||||||
head: ['ID', 'Account', 'Name', 'Core?', 'Balance', 'Kredits earned', 'Contributions count', 'IPFS']
|
head: ['ID', 'Account', 'Name', 'Core?', 'Balance', 'Kredits earned', 'Contributions count', 'IPFS']
|
||||||
|
|||||||
157
test/contracts/Contribution.js
Normal file
157
test/contracts/Contribution.js
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
const { expect } = require("chai");
|
||||||
|
const { ethers, upgrades } = require("hardhat");
|
||||||
|
const { anyValue } = require("@nomicfoundation/hardhat-chai-matchers/withArgs");
|
||||||
|
let owner, addr1, addr2, addr3, addr4, addr5, addr6, addr7;
|
||||||
|
let Contribution, Contributor;
|
||||||
|
|
||||||
|
describe("Contribution contract", async function () {
|
||||||
|
before(async function () {
|
||||||
|
[owner, addr1, addr2, addr3, addr4, addr5, addr6, addr7] = await ethers.getSigners();
|
||||||
|
let accounts = [owner, addr1, addr2, addr3, addr4, addr5, addr6, addr7];
|
||||||
|
const contributorFactory = await ethers.getContractFactory("Contributor");
|
||||||
|
Contributor = await upgrades.deployProxy(contributorFactory, ["0x2946fFfd31096435cb0fc927D306E1C006C5D1aF"]);
|
||||||
|
for (const account of accounts) {
|
||||||
|
await Contributor.addContributor(account.address, "0x99b8afd7b266e19990924a8be9099e81054b70c36b20937228a77a5cf75723b8", 18, 32);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("initialize()", function () {
|
||||||
|
before(async function () {
|
||||||
|
// const [owner] = await ethers.getSigners();
|
||||||
|
const contributionFactory = await ethers.getContractFactory("Contribution");
|
||||||
|
Contribution = await upgrades.deployProxy(contributionFactory, [40321]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets the veto confirmation period", async function () {
|
||||||
|
expect(await Contribution.blocksToWait()).to.equal(40321);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets the data migration flag", async function () {
|
||||||
|
expect(await Contribution.migrationDone()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets the deployer address", async function () {
|
||||||
|
expect(await Contribution.deployer()).to.equal(owner.address);
|
||||||
|
expect(await Contribution.deployer()).to.not.equal(addr1.address);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("finishMigration()", function () {
|
||||||
|
before(async function () {
|
||||||
|
const contributionFactory = await ethers.getContractFactory("Contribution");
|
||||||
|
Contribution = await upgrades.deployProxy(contributionFactory, [40321]);
|
||||||
|
await Contribution.setContributorContract(Contributor.address).then(res => res.wait())
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not allow random accounts to mark the migration as finished", async function () {
|
||||||
|
await expect(Contribution.connect(addr1).finishMigration()).to.be.revertedWith("Deployer only");
|
||||||
|
expect(await Contribution.migrationDone()).to.be.false;
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the deployer to mark the migration as finished", async function () {
|
||||||
|
await Contribution.finishMigration();
|
||||||
|
expect(await Contribution.migrationDone()).to.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("add()", function () {
|
||||||
|
before(async function () {
|
||||||
|
const contributionFactory = await ethers.getContractFactory("Contribution");
|
||||||
|
Contribution = await upgrades.deployProxy(contributionFactory, [40321]);
|
||||||
|
await Contribution.setContributorContract(Contributor.address).then(res => res.wait())
|
||||||
|
await Contribution.finishMigration();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not allow non-contributors to add a contribution", async function () {
|
||||||
|
await expect(Contribution.connect(addr7).add(
|
||||||
|
500, 1,
|
||||||
|
"0xe794f010e617449719c64076546254129f63a6d16cf200031afa646aeb35777f",
|
||||||
|
18, 32, 0, false
|
||||||
|
)).to.be.revertedWith("requires kredits or core status");
|
||||||
|
expect(await Contribution.contributionsCount()).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not allow special arguments outside of a migration", async function () {
|
||||||
|
await expect(Contribution.connect(addr7).add(
|
||||||
|
500, 1,
|
||||||
|
"0xe794f010e617449719c64076546254129f63a6d16cf200031afa646aeb35777f",
|
||||||
|
18, 32, 23000, true
|
||||||
|
)).to.be.revertedWith("extra arguments during migration only");
|
||||||
|
expect(await Contribution.contributionsCount()).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows core contributors to add a contribution", async function () {
|
||||||
|
await Contribution.connect(addr2).add(
|
||||||
|
2001, 1,
|
||||||
|
"0xe794f010e617449719c64076546254129f63a6d16cf200031afa646aeb35777f",
|
||||||
|
18, 32, 0, false
|
||||||
|
);
|
||||||
|
expect(await Contribution.contributionsCount()).to.equal(1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows contributors to add a contribution", async function () {
|
||||||
|
// Issue some kredits for new contributor #8 (addr7)
|
||||||
|
await Contribution.connect(addr1).add(
|
||||||
|
5000, 8,
|
||||||
|
"0xe794f010e617449719c64076546254129f63a6d16cf200031afa646aeb35777f",
|
||||||
|
18, 32, 0, false
|
||||||
|
);
|
||||||
|
await Contribution.connect(addr7).add(
|
||||||
|
1500, 1,
|
||||||
|
"0xe794f010e617449719c64076546254129f63a6d16cf200031afa646aeb35777f",
|
||||||
|
18, 32, 0, false
|
||||||
|
);
|
||||||
|
expect(await Contribution.contributionsCount()).to.equal(3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets confirmedAtBlock to current block plus blocksToWait", async function () {
|
||||||
|
await Contribution.connect(addr1).add(
|
||||||
|
500, 3,
|
||||||
|
"0xe794f010e617449719c64076546254129f63a6d16cf200031afa646aeb35777f",
|
||||||
|
18, 32, 0, false
|
||||||
|
);
|
||||||
|
expect(await Contribution.contributionsCount()).to.equal(4);
|
||||||
|
const c = await Contribution.getContribution(4);
|
||||||
|
const currentBlockNumber = await kredits.provider.getBlockNumber();
|
||||||
|
expect(c['confirmedAtBlock']).to.equal(currentBlockNumber + 1 + 40321);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("emits a ContributionAdded event", async function () {
|
||||||
|
await expect(Contribution.connect(addr1).add(
|
||||||
|
2001, 1,
|
||||||
|
"0xe794f010e617449719c64076546254129f63a6d16cf200031afa646aeb35777f",
|
||||||
|
18, 32, 0, false
|
||||||
|
)).to.emit(Contribution, "ContributionAdded").withArgs(anyValue, 1, 2001);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with extra arguments during migration", async function () {
|
||||||
|
before(async function () {
|
||||||
|
const contributionFactory = await ethers.getContractFactory("Contribution");
|
||||||
|
Contribution = await upgrades.deployProxy(contributionFactory, [40321]);
|
||||||
|
await Contribution.setContributorContract(Contributor.address);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows to add a contribution with custom confirmedAtBlock", async function () {
|
||||||
|
await Contribution.connect(addr2).add(
|
||||||
|
2001, 1,
|
||||||
|
"0xe794f010e617449719c64076546254129f63a6d16cf200031afa646aeb35777f",
|
||||||
|
18, 32, 23000, false
|
||||||
|
);
|
||||||
|
expect(await Contribution.contributionsCount()).to.equal(1);
|
||||||
|
const c = await Contribution.getContribution(1);
|
||||||
|
expect(c['confirmedAtBlock'].toNumber()).to.equal(23000);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows to add a vetoed contribution", async function () {
|
||||||
|
await Contribution.connect(addr2).add(
|
||||||
|
2001, 1,
|
||||||
|
"0xe794f010e617449719c64076546254129f63a6d16cf200031afa646aeb35777f",
|
||||||
|
18, 32, 23000, true
|
||||||
|
);
|
||||||
|
expect(await Contribution.contributionsCount()).to.equal(2);
|
||||||
|
const c = await Contribution.getContribution(2);
|
||||||
|
expect(c['vetoed']).to.be.true;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
122
test/contracts/Contributor.js
Normal file
122
test/contracts/Contributor.js
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
const { expect } = require("chai");
|
||||||
|
const { ethers, upgrades } = require("hardhat");
|
||||||
|
let owner, addr1, addr2, addr3, addr4, addr5, addr6, addr7, addr8;
|
||||||
|
let Contribution, Contributor, Token;
|
||||||
|
|
||||||
|
describe("Contributor contract", async function () {
|
||||||
|
before(async function () {
|
||||||
|
[owner, addr1, addr2, addr3, addr4, addr5, addr6, addr7, addr8] = await ethers.getSigners();
|
||||||
|
|
||||||
|
const contributorFactory = await ethers.getContractFactory("Contributor");
|
||||||
|
Contributor = await upgrades.deployProxy(contributorFactory, [addr8.address]);
|
||||||
|
const contributionFactory = await ethers.getContractFactory("Contribution");
|
||||||
|
Contribution = await upgrades.deployProxy(contributionFactory, [40321]);
|
||||||
|
const tokenFactory = await ethers.getContractFactory("Token");
|
||||||
|
Token = await upgrades.deployProxy(tokenFactory);
|
||||||
|
|
||||||
|
await Contributor.setTokenContract(Token.address);
|
||||||
|
await Contributor.setContributionContract(Contribution.address);
|
||||||
|
await Contribution.setContributorContract(Contributor.address);
|
||||||
|
await Token.setContributorContract(Contributor.address);
|
||||||
|
|
||||||
|
let accounts = [owner, addr1, addr2, addr3, addr4, addr5, addr6, addr7];
|
||||||
|
for (const account of accounts) {
|
||||||
|
await Contributor.addContributor(account.address, "0x99b8afd7b266e19990924a8be9099e81054b70c36b20937228a77a5cf75723b8", 18, 32);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("initialize()", function () {
|
||||||
|
it("sets the deployer address", async function () {
|
||||||
|
expect(await Contributor.deployer()).to.equal(owner.address);
|
||||||
|
expect(await Contributor.deployer()).to.not.equal(addr1.address);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("sets a profile manager address", async function () {
|
||||||
|
expect(await Contributor.profileManager()).to.equal(addr8.address);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("add()", function () {
|
||||||
|
it("does not allow random accounts to create a contributor profile", async function () {
|
||||||
|
await expect(Contributor.connect(addr7).addContributor(
|
||||||
|
"0x608FD4b95116Ea616990Aaeb1d4f1ce07612f261",
|
||||||
|
"0x1d9de6de5c72eedca6d7a5e8a9159e2f5fe676506aece3000acefcc821723429",
|
||||||
|
18, 32
|
||||||
|
)).to.be.revertedWith("Only core and profile manager");
|
||||||
|
expect(await Contributor.contributorsCount()).to.equal(8);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows core contributors to create a contributor profile", async function () {
|
||||||
|
await Contributor.connect(addr1).addContributor(
|
||||||
|
"0x608FD4b95116Ea616990Aaeb1d4f1ce07612f261",
|
||||||
|
"0x1d9de6de5c72eedca6d7a5e8a9159e2f5fe676506aece3000acefcc821723429",
|
||||||
|
18, 32
|
||||||
|
);
|
||||||
|
expect(await Contributor.contributorsCount()).to.equal(9);
|
||||||
|
const c = await Contributor.getContributorById(9);
|
||||||
|
expect(c['account']).to.equal("0x608FD4b95116Ea616990Aaeb1d4f1ce07612f261");
|
||||||
|
expect(c['kreditsWithdrawn']).to.equal(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("does not allow to create accounts with an existing address", async function () {
|
||||||
|
await expect(Contributor.connect(addr1).addContributor(
|
||||||
|
"0x608FD4b95116Ea616990Aaeb1d4f1ce07612f261",
|
||||||
|
"0x1d9de6de5c72eedca6d7a5e8a9159e2f5fe676506aece3000acefcc821723429",
|
||||||
|
18, 32
|
||||||
|
)).to.be.revertedWith("Address already in use");
|
||||||
|
expect(await Contributor.contributorsCount()).to.equal(9);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("emits a ContributorAdded event", async function () {
|
||||||
|
await expect(Contributor.connect(addr1).addContributor(
|
||||||
|
"0x765E88b4F9a59C3a3b300C6eFF9E6E9fDDf9FbD9",
|
||||||
|
"0xcfbeeadc244dfdc55bbad50d431871439df067970db84c73023956c96a6f5df2",
|
||||||
|
18, 32
|
||||||
|
)).to.emit(Contributor, "ContributorAdded").withArgs(10, "0x765E88b4F9a59C3a3b300C6eFF9E6E9fDDf9FbD9");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("allows the profile manager account to create a contributor profile", async function () {
|
||||||
|
await Contributor.connect(addr8).addContributor(
|
||||||
|
"0x954712B8703Df5255A219B139ba7BFC256E72a15",
|
||||||
|
"0x1d9de6de5c72eedca6d7a5e8a9159e2f5fe676506aece3000acefcc821723429",
|
||||||
|
18, 32
|
||||||
|
);
|
||||||
|
expect(await Contributor.contributorsCount()).to.equal(11);
|
||||||
|
const c = await Contributor.getContributorById(11);
|
||||||
|
expect(c['account']).to.equal("0x954712B8703Df5255A219B139ba7BFC256E72a15");
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("withdraw()", function () {
|
||||||
|
before(async function () {
|
||||||
|
// Add some pre-confirmed contributions (confirmedAtBlock 1)
|
||||||
|
await Contribution.add(
|
||||||
|
1500, 2, "0xe794f010e617449719c64076546254129f63a6d16cf200031afa646aeb35777f",
|
||||||
|
18, 32, 1, false
|
||||||
|
);
|
||||||
|
await Contribution.add(
|
||||||
|
5000, 2, "0xe794f010e617449719c64076546254129f63a6d16cf200031afa646aeb35777f",
|
||||||
|
18, 32, 1, false
|
||||||
|
);
|
||||||
|
await Contribution.finishMigration();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requires the transaction sender to be a contributor", async function () {
|
||||||
|
await expect(Contributor.withdraw()).to.be.revertedWith("Contributors only");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("executes a withdrawal of all available ERC20 kredits", async function () {
|
||||||
|
let c = await Contributor.getContributorById(2);
|
||||||
|
expect(c['balance']).to.equal(0);
|
||||||
|
await Contributor.connect(addr1).withdraw();
|
||||||
|
c = await Contributor.getContributorById(2);
|
||||||
|
expect(c['balance']).to.equal(6500);
|
||||||
|
expect(c['kreditsWithdrawn']).to.equal(6500);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("requires the withdrawable amount to be larger than 0", async function () {
|
||||||
|
await expect(Contributor.connect(addr1).withdraw()).to.be.revertedWith("No kredits available");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user