Merge pull request 'Add export/import functionality' (#224) from feature/export-import into master
All checks were successful
continuous-integration/drone/push Build is passing

Reviewed-on: #224
This commit is contained in:
Râu Cao 2022-09-02 20:31:27 +00:00
commit 1c097f37a6
22 changed files with 1187 additions and 223 deletions

View File

@ -24,7 +24,7 @@ steps:
- name: build contracts - name: build contracts
image: gitea.kosmos.org/kredits/docker-ci:latest image: gitea.kosmos.org/kredits/docker-ci:latest
commands: commands:
- su drone -c 'npm run devchain' & - su drone -c 'npm run devchain -- --silent' &
- sleep 5 - sleep 5
- su drone -c 'npm run build' - su drone -c 'npm run build'
depends_on: depends_on:
@ -32,7 +32,7 @@ steps:
- name: test - name: test
image: gitea.kosmos.org/kredits/docker-ci:latest image: gitea.kosmos.org/kredits/docker-ci:latest
commands: commands:
- su drone -c 'npm run devchain' & - su drone -c 'npm run devchain -- --silent' &
- sleep 5 - sleep 5
- su drone -c 'npm test' - su drone -c 'npm test'
depends_on: depends_on:

View File

@ -1 +1,2 @@
/scripts/ /scripts/
/test/

2
.gitignore vendored
View File

@ -5,6 +5,8 @@ node_modules
yarn-error.log yarn-error.log
.DS_Store .DS_Store
data
cache cache
artifacts artifacts
.openzeppelin .openzeppelin

View File

@ -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;
} }
} }

View File

@ -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,9 @@ interface IContributionBalance {
} }
contract Contributor is Initializable { contract Contributor is Initializable {
address deployer; address public deployer;
IContributionBalance public contributionContract; IContributionBalance public contributionContract;
ITokenBalance public tokenContract; IToken public tokenContract;
struct Contributor { struct Contributor {
address account; address account;
@ -21,6 +22,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,6 +38,11 @@ contract Contributor is Initializable {
_; _;
} }
modifier onlyContributors {
require(addressExists(msg.sender) && contributionContract.balanceOf(msg.sender) > 0, "Contributors only");
_;
}
function initialize() public initializer { function initialize() public initializer {
deployer = msg.sender; deployer = msg.sender;
} }
@ -47,7 +54,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) {
@ -81,7 +88,7 @@ contract Contributor is Initializable {
} }
function addContributor(address account, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public onlyCore { function addContributor(address account, bytes32 hashDigest, uint8 hashFunction, uint8 hashSize) public onlyCore {
require(!addressExists(account)); require(!addressExists(account), "Address already in use");
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 +97,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 +140,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 +152,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);
}
} }

View File

@ -7,17 +7,15 @@ 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");
@ -27,21 +25,17 @@ contract Token is Initializable, ERC20Upgradeable {
return 0; return 0;
} }
function setContributionContract(address contribution) public {
require(address(contributionContract) == address(0) || contributorContract.addressIsCore(msg.sender), "Core only");
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");
_mint(contributorAccount, amount); _mint(contributorAccount, amount);
emit LogMint(contributorAccount, amount, contributionId); emit KreditsMinted(contributorAccount, amount);
} }
} }

View File

@ -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

View File

@ -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"}]

View File

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

View File

@ -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);
}); });
} }

829
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -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",

View File

@ -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);
@ -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)

View 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();

View 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();

View File

@ -0,0 +1,39 @@
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}`);
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
);
// await result.wait();
console.log(`Added contribution #${contributionId}: ${result.hash}`);
};
} catch(e) {
console.log(e);
}
}
main();

View File

@ -0,0 +1,33 @@
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}`);
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,
);
// await result.wait();
console.log(`Added contributor #${contributorId}: ${result.hash}`);
};
} catch(e) {
console.log(e);
}
}
main();

View File

@ -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
]) ])
}); });

View 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);
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;
});
});
});
});

View File

@ -0,0 +1,106 @@
const { expect } = require("chai");
const { ethers, upgrades } = require("hardhat");
let owner, addr1, addr2, addr3, addr4, addr5, addr6, addr7;
let Contribution, Contributor, Token;
describe("Contributor contract", async function () {
before(async function () {
[owner, addr1, addr2, addr3, addr4, addr5, addr6, addr7] = await ethers.getSigners();
const contributorFactory = await ethers.getContractFactory("Contributor");
Contributor = await upgrades.deployProxy(contributorFactory);
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);
});
});
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("Core only");
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");
});
});
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");
});
});
});