Merge branch 'master' into chore/linter

# Conflicts:
#	lib/contracts/contribution.js
#	lib/contracts/contributor.js
#	lib/contracts/proposal.js
#	lib/kredits.js
#	lib/serializers/contributor.js
#	yarn.lock
This commit is contained in:
fsmanuel 2019-04-24 19:12:18 +02:00
commit 145b3ea766
31 changed files with 32965 additions and 120 deletions

2
.gitignore vendored
View File

@ -4,3 +4,5 @@ node_modules
**/node_modules
.ganache-db
.tm_properties
yarn-error.log
.DS_Store

View File

@ -36,7 +36,7 @@ development ganache.
$ npm run devchain (or aragon devchain --port 7545)
To clear/reset the chain use:
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)
@ -174,6 +174,26 @@ Runs `npm install` for each app and publishes a new version.
or
$ npm run deploy:apps
## Deployment
### Apps deployment
To deploy a new app version run:
$ aragon apm publish major --environment=NETWORK_TO_DEPLOY
### KreditsKit
deploy the KreditsKit as Kit to create new DAOs
$ truffle exec scripts/deploy-kit.js --network=NETWORK_TO_DEPLOY
### Creating a new DAO
make sure all apps and the KreditsKit are deployed, then create a new DAO:
$ truffle exec scripts/new-dao.js --network=NETWORK_TO_DEPLOY
## ACL / Permissions
## Upgradeable contracts

View File

@ -118,6 +118,26 @@ contract Contribution is AragonApp {
// Custom functions
//
function totalKreditsEarned(bool confirmedOnly) public view returns (uint256 count) {
for (uint32 i = 1; i <= contributionsCount; i++) {
ContributionData memory c = contributions[i];
if (block.number >= c.confirmedAtBlock || !confirmedOnly) {
count += c.amount; // should use safemath
}
}
}
function totalKreditsEarnedByContributor(uint32 contributorId, bool confirmedOnly) public view returns (uint256 count) {
uint256 tokenBalance = ownedContributions[contributorId].length;
for (uint256 i = 0; i < tokenBalance; i++) {
uint32 cId = ownedContributions[contributorId][i];
ContributionData memory c = contributions[cId];
if (block.number >= c.confirmedAtBlock || !confirmedOnly) {
count += c.amount; // should use safemath
}
}
}
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) {
id = contributionId;
ContributionData storage c = contributions[id];

8145
apps/contribution/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,10 @@ 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 (uint256 count);
function balanceOf(address owner) public view returns (uint256);
}
contract Contributor is AragonApp {
bytes32 public constant KERNEL_APP_ADDR_NAMESPACE = 0xd6f028ca0e8edb4a8c9757ca4fdccab25fa1e0317da1188108f7d2dee14902fb;
@ -43,6 +47,12 @@ contract Contributor is AragonApp {
return k.getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[uint8(Apps.Token)]);
}
function getContributionContract() public view returns (address) {
IKernel k = IKernel(kernel());
return k.getApp(KERNEL_APP_ADDR_NAMESPACE, appIds[uint8(Apps.Contribution)]);
}
function coreContributorsCount() view public returns (uint32) {
uint32 count = 0;
for (uint32 i = 1; i <= contributorsCount; i++) {
@ -118,7 +128,7 @@ contract Contributor is AragonApp {
return contributors[id];
}
function getContributorById(uint32 _id) public view returns (uint32 id, address account, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, bool isCore, uint256 balance, bool exists ) {
function getContributorById(uint32 _id) public view returns (uint32 id, address account, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize, bool isCore, uint256 balance, uint256 totalKreditsEarned, uint256 contributionsCount, bool exists ) {
id = _id;
Contributor storage c = contributors[_id];
account = c.account;
@ -128,6 +138,9 @@ contract Contributor is AragonApp {
isCore = isCoreTeam(id);
address token = getTokenContract();
balance = ITokenBalance(token).balanceOf(c.account);
address contribution = getContributionContract();
totalKreditsEarned = IContributionBalance(contribution).totalKreditsEarnedByContributor(_id, true);
contributionsCount = IContributionBalance(contribution).balanceOf(c.account);
exists = c.exists;
}

8145
apps/contributor/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

8145
apps/proposal/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

8145
apps/token/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,36 @@
const contractCalls = [
['Contributor', 'add', [{ account: '0x7e8f313c56f809188313aa274fa67ee58c31515d', name: 'bumi', kind: 'person', url: '', github_username: 'bumi', github_uid: 318, wiki_username: 'bumi' }, { gasLimit: 200000 }]],
['Contributor', 'add', [{ account: '0x49575f3DD9a0d60aE661BC992f72D837A77f05Bc', name: 'raucao', kind: 'person', url: '', github_username: 'skddc', github_uid: 842, wiki_username: 'raucau' }, { gasLimit: 200000 }]],
['Contributor', 'add', [{
account: '0x7e8f313c56f809188313aa274fa67ee58c31515d',
name: 'bumi',
kind: 'person',
url: '',
github_username: 'bumi',
github_uid: 318,
gitea_username: 'bumi',
wiki_username: 'Bumi'
}, { gasLimit: 200000 }]],
['Contributor', 'add', [{
account: '0x49575f3DD9a0d60aE661BC992f72D837A77f05Bc',
name: 'raucao',
kind: 'person',
url: '',
github_username: 'skddc',
github_uid: 842,
gitea_username: 'raucao',
wiki_username: 'Basti'
}, { gasLimit: 200000 }]],
['Contributor', 'add', [{
account: '0xF722709ECC3B05c19d02E82a2a4A4021B8F48C62',
name: 'Manuel',
kind: 'person',
url: '',
github_username: 'fsmanuel',
github_uid: 54812,
wiki_username: 'Manuel'
}, { gasLimit: 200000 }]],
['Proposal', 'addProposal', [{ contributorId: 1, contributorIpfsHash: 'QmWKCYGr2rSf6abUPaTYqf98urvoZxGrb7dbspFZA6oyVF', date: '2019-04-09', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Ran the seeds', url: '' }, { gasLimit: 350000 }]],
['Proposal', 'addProposal', [{ contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-10', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Ran the seeds', url: '' }, { gasLimit: 350000 }]],
['Proposal', 'addProposal', [{ contributorId: 2, contributorIpfsHash: 'QmcHzEeAM26HV2zHTf5HnZrCtCtGdEccL5kUtDakAB7ozB', date: '2019-04-11', amount: 500, kind: 'dev', description: '[67P/kredits-contracts] Hacked on kredits', url: '' }, { gasLimit: 350000 }]],

View File

@ -1,5 +1,22 @@
# Contribution deployments
aragon apm publish major --environment=rinkeby"
## 2019-04-24 update balances
✔ Successfully published kredits-contribution.open.aragonpm.eth v6.0.0:
Contract address: 0x2c083EEA83fd3a99C93759D97D0317A43261c758
Content (ipfs): QmULpSqz7BgTFmDu8AL7YZZEz525xkcEzf3dPKtbRdUtFs
Transaction hash: 0x8b01c4c00162e918659d267a2beaf33b578e2aaf9f427f1aa9a43029333c5cd7
## 2019-04-10 - Weltempfänger release
✔ Successfully published kredits-contribution.open.aragonpm.eth v5.0.0:
Contract address: 0xe0f7dB486321b917e3A986Bdb2F2b9d51BA98fa9
Content (ipfs): QmU3XEBb4f5jU8MFFEpwaa95C1mhc82UeYLRWLrKsvcQNw
Transaction hash: 0xd736ff5f79f8142be3fad1a50580fb40aa468838da397f8630285fd91a445af3
## 2019-04-04
✔ Successfully published kredits-contribution.open.aragonpm.eth v4.0.0:

View File

@ -1,5 +1,23 @@
# Contributor deployments
aragon apm publish major --environment=rinkeby
## 2019-04-29 update balances
✔ Successfully published kredits-contributor.open.aragonpm.eth v5.0.0:
Contract address: 0xadefa3b66b68a127Fe38bEa1813b844EE69CFD86
Content (ipfs): QmeygbQgoj2McLWzo9hJayLWuBZqFaK4HTpa5qLeQdkn5K
Transaction hash: 0x4237a9636f6e4a8190e0d5bcfa85a452da097bf654a173a88e0e1de3d078f08d
## 2019-04-10 - Weltempfänger release
✔ Successfully published kredits-contributor.open.aragonpm.eth v4.0.0:
Contract address: 0x08a6D4D915FCAA5524F05F5F715a6C17cB6eeA6B
Content (ipfs): QmR62PWwe1EzommfkhJDYcTvHoZjbXuv9dTG6vCn5dWCsb
Transaction hash: 0xd5317c9e207a413485c55ec3046b09d467d978443680304737a6d7d3db0c90e1
## 2019-04-04
✔ Successfully published kredits-contributor.open.aragonpm.eth v3.0.0:

View File

@ -1,5 +1,24 @@
# Kredits deployment
## 2019-04-24 upgrade contributor and contribution
aragon dao upgrade 0xc34edf7d11b7f8433d597f0bb0697acdff55ef14 kredits-contributor.open.aragonpm.eth --environment=rinkeby
eth-provider | Invalid provider preset/location: "local"
✔ Fetching kredits-contributor.open.aragonpm.eth@latest
✔ Upgrading app
✔ Successfully executed: "Set the resolving address of 'kredits-contributor.open.aragonpm.eth' in namespace 'App code' to 0xadefa3b66b68a127Fe38bEa1813b844EE69CFD86"
aragon dao upgrade 0xc34edf7d11b7f8433d597f0bb0697acdff55ef14 kredits-contribution.open.aragonpm.eth --environment=rinkeby
✔ Fetching kredits-contribution.open.aragonpm.eth@latest
✔ Upgrading app
✔ Successfully executed: "Set the resolving address of 'kredits-contribution.open.aragonpm.eth' in namespace 'App code' to 0x2c083EEA83fd3a99C93759D97D0317A43261c758"
## 2019-04-10 - Weltempfänger release
Using KreditsKit at: 0x76e069b47b79442657eaf0555a32c6b16fa1b8b4
Created new DAO at: 0xc34edf7d11b7f8433d597f0bb0697acdff55ef14
## 2019-04-04
Using KreditsKit at: 0x76e069b47b79442657eaf0555a32c6b16fa1b8b4

View File

@ -1,5 +1,13 @@
# Proposal deployments
## 2019-04-10 - Weltempfänger release
✔ Successfully published kredits-proposal.open.aragonpm.eth v5.0.0:
Contract address: 0x4ce5b0286483c66b861e5599a199054687434552
Content (ipfs): QmNYXEcmvKTGxYiob7WUf85oZhdmFDCuGiA6TsRrDE9TYb
Transaction hash: 0x0482b58a1ba87d494c6391026399d0ac41b45384330d916f3f99ba70e501584b
## 2019-04-04
✔ Successfully published kredits-proposal.open.aragonpm.eth v4.0.0:

View File

@ -1,5 +1,13 @@
# Token deployments
## 2019-04-10 - Weltempfänger release
✔ Successfully published kredits-token.open.aragonpm.eth v4.0.0:
Contract address: 0x05E0C2bbdA8e5BeE22AC1E20C1457dA4de63aE26
Content (ipfs): QmUuYLRMRNZcundUk2pxVaSjMNvtB7hzdf3eyoaUNqDPow
Transaction hash: 0x98c28b5ca645904d56eb83c4783682f018c0fcee015b3a3d5fa8bd609223fb89
## 2019-04-04
✔ Successfully published kredits-token.open.aragonpm.eth v3.0.0:

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,3 @@
{
"4": "0x76e069b47b79442657eaf0555a32c6b16fa1b8b4",
"41787949": "0xa35aacdfccac54d3d96e0d29050c773b251c2c83"
"4": "0x76e069b47b79442657eaf0555a32c6b16fa1b8b4"
}

View File

@ -1,4 +1,3 @@
{
"4": "0xcd75458fbc4aa2231252d5b21f1391fd031e5cb2",
"41787949": "0x183af3950364390a266edff2a0e7c4c2f95c0691"
"4": "0xc34edf7d11b7f8433d597f0bb0697acdff55ef14"
}

View File

@ -1,19 +1,9 @@
const Record = require('./record');
const ContributionSerializer = require('../serializers/contribution');
const Base = require('./base');
class Contribution extends Base {
all () {
return this.functions.contributionsCount()
.then(async (count) => {
let contributions = [];
for (let id = 1; id <= count; id++) {
const contribution = await this.getById(id);
contributions.push(contribution);
}
return contributions;
});
class Contribution extends Record {
get count () {
return this.functions.contributionsCount();
}
getById (id) {

View File

@ -1,25 +1,16 @@
const RSVP = require('rsvp');
const Record = require('./record');
const ContributorSerializer = require('../serializers/contributor');
const Base = require('./base');
const formatKredits = require('../utils/format-kredits');
class Contributor extends Base {
all () {
return this.functions.contributorsCount()
.then(count => {
let contributors = [];
for (let id = 1; id <= count; id++) {
contributors.push(this.getById(id));
}
return RSVP.all(contributors);
});
class Contributor extends Record {
get count () {
return this.functions.contributorsCount();
}
getById (id) {
return this.functions.getContributorById(id)
.then((data) => {
.then(data => {
data.balanceInt = formatKredits(data.balance);
return this.ipfs.catAndMerge(data, ContributorSerializer.deserialize);
});
}
@ -48,12 +39,16 @@ class Contributor extends Base {
});
}
add (contributorAttr, callOptions = {}) {
let json = ContributorSerializer.serialize(contributorAttr);
// TODO: validate against schema
async add (contributorAttr, callOptions = {}) {
let contributor = new ContributorSerializer(contributorAttr);
try { await contributor.validate(); }
catch (error) { return Promise.reject(error); }
const jsonStr = contributor.serialize();
return this.ipfs
.add(json)
.add(jsonStr)
.then((ipfsHashAttr) => {
let contributor = [
contributorAttr.account,
@ -65,6 +60,30 @@ class Contributor extends Base {
return this.functions.addContributor(...contributor, callOptions);
});
}
updateProfile (contributorId, updateAttr, callOptions = {}) {
return this.getById(contributorId).then(async (contributor) => {
let updatedContributorAttr = Object.assign(contributor, updateAttr)
let updatedContributor = new ContributorSerializer(updatedContributorAttr);
try { await updatedContributor.validate(); }
catch (error) { return Promise.reject(error); }
const jsonStr = updatedContributor.serialize();
return this.ipfs
.add(jsonStr)
.then(ipfsHashAttr => {
return this.functions.updateContributorProfileHash(
contributorId,
ipfsHashAttr.hashDigest,
ipfsHashAttr.hashFunction,
ipfsHashAttr.hashSize,
callOptions
);
});
});
}
}
module.exports = Contributor;

View File

@ -1,20 +1,9 @@
const RSVP = require('rsvp');
const Record = require('./record');
const ContributionSerializer = require('../serializers/contribution');
const Base = require('./base');
class Proposal extends Base {
all () {
return this.functions.proposalsCount()
.then(count => {
let proposals = [];
for (let id = 1; id <= count; id++) {
proposals.push(this.getById(id));
}
return RSVP.all(proposals);
});
class Proposal extends Record {
get count () {
return this.functions.proposalsCount();
}
getById (id) {

14
lib/contracts/record.js Normal file
View File

@ -0,0 +1,14 @@
const Base = require('./base');
const paged = require('../utils/pagination');
class Record extends Base {
all(options = {}) {
return this.count
.then((count) => {
let records = paged(count, options).map((id) => this.getById(id));
return Promise.all(records);
});
}
}
module.exports = Record

View File

@ -1,5 +1,4 @@
const ethers = require('ethers');
const RSVP = require('rsvp');
const Preflight = require('./utils/preflight');
@ -57,7 +56,8 @@ class Kredits {
);
});
});
return RSVP.all(addressPromises).then(() => { return this; });
return Promise.all(addressPromises).then(() => { return this });
});
}

View File

@ -1,3 +1,5 @@
const schemas = require('kosmos-schemas');
const validator = require('../utils/validator');
/**
* Handle serialization for JSON-LD object of the contributor, according to
* https://github.com/67P/kosmos-schemas/blob/master/schemas/contributor.json
@ -6,41 +8,9 @@
* @public
*/
class Contributor {
/**
* Deserialize JSON to object
*
* @method
* @public
*/
static deserialize (serialized) {
let {
name,
kind,
url,
accounts,
} = JSON.parse(serialized.toString('utf8'));
let github_username, github_uid, wiki_username;
let github = accounts.find((a) => a.site === 'github.com');
let wiki = accounts.find((a) => a.site === 'wiki.kosmos.org');
if (github) {
(({ username: github_username, uid: github_uid} = github));
}
if (wiki) {
(({ username: wiki_username } = wiki));
}
return {
name,
kind,
url,
accounts,
github_uid,
github_username,
wiki_username,
ipfsData: serialized,
};
constructor (attrs) {
Object.keys(attrs).forEach(a => this[a] = attrs[a]);
}
/**
@ -49,15 +19,16 @@ class Contributor {
* @method
* @public
*/
static serialize (deserialized) {
serialize () {
let {
name,
kind,
url,
github_uid,
github_username,
gitea_username,
wiki_username,
} = deserialized;
} = this;
let data = {
"@context": "https://schema.kosmos.org",
@ -80,6 +51,14 @@ class Contributor {
});
}
if (gitea_username) {
data.accounts.push({
"site": "gitea.kosmos.org",
"username": gitea_username,
"url": `https://gitea.kosmos.org/${gitea_username}`
});
}
if (wiki_username) {
data.accounts.push({
"site": "wiki.kosmos.org",
@ -91,6 +70,60 @@ class Contributor {
// Write it pretty to ipfs
return JSON.stringify(data, null, 2);
}
/**
* Validate serialized data against schema
*
* @public
*/
validate () {
const serialized = JSON.parse(this.serialize());
const valid = validator.validate(serialized, schemas['contributor']);
return valid ? Promise.resolve() : Promise.reject(validator.error);
}
/**
* Deserialize JSON to object
*
* @method
* @public
*/
static deserialize (serialized) {
let {
name,
kind,
url,
accounts,
} = JSON.parse(serialized.toString('utf8'));
let github_username, github_uid, gitea_username, wiki_username;
let github = accounts.find(a => a.site === 'github.com');
let gitea = accounts.find(a => a.site === 'gitea.kosmos.org');
let wiki = accounts.find(a => a.site === 'wiki.kosmos.org');
if (github) {
(({ username: github_username, uid: github_uid} = github));
}
if (gitea) {
(({ username: gitea_username } = gitea));
}
if (wiki) {
(({ username: wiki_username } = wiki));
}
return {
name,
kind,
url,
accounts,
github_uid,
github_username,
gitea_username,
wiki_username,
ipfsData: serialized,
};
}
}
module.exports = Contributor;

View File

@ -0,0 +1,10 @@
const ethersUtils = require('ethers').utils;
module.exports = function (value, options = {}) {
let etherValue = ethersUtils.formatEther(value);
if (options.asFloat) {
return parseFloat(etherValue);
} else {
return parseInt(etherValue);
}
}

46
lib/utils/pagination.js Normal file
View File

@ -0,0 +1,46 @@
function pageNumber(number, size, recordCount) {
let numberOfPages = Math.ceil(recordCount / size);
number = parseInt(number) || 1;
// Ensure page number is in range
number = number < 1 ? 1 : number;
number = number > numberOfPages ? numberOfPages : number;
return number;
}
function buildIds(order, number, size, recordCount) {
let offset = size * (number - 1);
let start;
let mapFunction;
if (order === 'asc') {
start = 1 + offset;
mapFunction = (_, i) => start + i;
} else {
start = recordCount - offset;
mapFunction = (_, i) => start - i;
}
// Ensure size is in range
let end = offset + size;
if (end > recordCount) {
let diff = end - recordCount;
size = size - diff;
}
return Array.from({ length: size }, mapFunction);
}
module.exports = function paged(recordCount, options = {}) {
let { order, page } = options;
order = order || 'desc';
page = page || {};
let size = parseInt(page.size) || 25;
let number = pageNumber(page.number, size, recordCount);
return buildIds(order, number, size, recordCount);
};

7
package-lock.json generated
View File

@ -1,6 +1,6 @@
{
"name": "kredits-contracts",
"version": "5.0.0",
"version": "5.3.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@ -11776,8 +11776,9 @@
}
},
"kosmos-schemas": {
"version": "github:67P/kosmos-schemas#28a5e9f1fc2e083165e4e02ce3361c3f4fafa592",
"from": "github:67P/kosmos-schemas#feature/contribution_date_time",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/kosmos-schemas/-/kosmos-schemas-2.0.0.tgz",
"integrity": "sha512-zIjWcDxaN94m1vPgUaI5LRX6y07Ihw9ScPoGKf1NkZ0sLgD/CRV8YIKRyDafH5mThe3uBN2+F6H6Gp5qhNhALg==",
"requires": {
"brfs-babel": "^1.0.0"
}

View File

@ -1,6 +1,6 @@
{
"name": "kredits-contracts",
"version": "5.0.0",
"version": "5.3.0",
"description": "Ethereum contracts and npm wrapper for Kredits",
"main": "./lib/kredits.js",
"directories": {
@ -52,8 +52,7 @@
"dependencies": {
"ethers": "^4.0.27",
"ipfs-http-client": "^30.1.1",
"kosmos-schemas": "github:67P/kosmos-schemas#feature/contribution_date_time",
"rsvp": "^4.8.2",
"kosmos-schemas": "^2.0.0",
"tv4": "^1.3.0"
},
"keywords": [

View File

@ -26,7 +26,7 @@ module.exports = async function(callback) {
kind: await prompt('Kind (default person): ', {default: 'person'}),
url: await prompt('URL: '),
github_username: await prompt('GitHub username: '),
github_uid: await prompt('GitHub UID: '),
github_uid: parseInt(await prompt('GitHub UID: ')),
wiki_username: await prompt('Wiki username: '),
};

View File

@ -22,6 +22,7 @@ module.exports = async function(callback) {
let blockNumber = await kredits.provider.getBlockNumber();
let contributions = await kredits.Contribution.all();
console.log(`Current block number: ${blockNumber}`);
contributions.forEach((c) => {
const confirmed = c.confirmedAtBlock <= blockNumber;
@ -30,7 +31,7 @@ module.exports = async function(callback) {
c.contributorId,
`${c.description}`,
c.amount.toString(),
confirmed,
`${confirmed} (${c.confirmedAtBlock})`,
c.vetoed,
c.claimed,
c.ipfsHash
@ -38,6 +39,9 @@ module.exports = async function(callback) {
});
console.log(table.toString());
let totalKreditsEarned = await kredits.Contribution.functions.totalKreditsEarned(true);
console.log(`Total confirmed kredits: ${totalKreditsEarned}`);
} catch (err) {
console.log(err);
}

View File

@ -15,24 +15,31 @@ module.exports = async function(callback) {
console.log(`Using Contributor at: ${kredits.Contributor.contract.address}`);
const table = new Table({
head: ['ID', 'Account', 'Name', 'Core?', 'Balance', 'IPFS']
head: ['ID', 'Account', 'Name', 'Core?', 'Balance', 'Kredits earned', 'Contributions count', 'IPFS']
})
let contributors = await kredits.Contributor.all()
try {
const contributors = await kredits.Contributor.all()
contributors.forEach((c) => {
table.push([
c.id.toString(),
c.account,
`${c.name}`,
c.isCore,
c.balanceInt.toString(),
c.totalKreditsEarned.toString(),
c.contributionsCount.toString(),
c.ipfsHash
])
})
console.log(table.toString())
} catch(e) {
callback(e);
return;
}
contributors.forEach((c) => {
table.push([
c.id.toString(),
c.account,
`${c.name}`,
c.isCore,
ethers.utils.formatEther(c.balance),
c.ipfsHash
])
})
console.log(table.toString())
callback()
}