Commit 84e84a81 authored by Anastasios Kalogeropoulos's avatar Anastasios Kalogeropoulos
Browse files

Project added

parents
PROJECT_PATH=./
MNEMONIC=season prevent fault almost then hungry lazy typical pipe exist recipe milk
\ No newline at end of file
[submodule "FIN4Contracts"]
path = FIN4Contracts
url = https://github.com/johnrachwan123/FIN4Contracts.git
[submodule "FIN4Xplorer"]
path = FIN4Xplorer
url = https://github.com/johnrachwan123/FIN4Xplorer.git
[submodule "ethereum-bridge"]
path = ethereum-bridge
url = https://github.com/moritz-schindelmann-tum/ethereum-bridge.git
.env/
.idea/
package-lock.json
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
**/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
bigchaindb/
FROM node:12
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
COPY package*.json ./
COPY contracts ./contracts/
COPY migrations ./migrations/
COPY truffle-config.js ./
COPY verifiers.js ./
COPY config.json ./
COPY compile-and-migrate.sh ./
RUN chmod ugo+rwx compile-and-migrate.sh
RUN npm install --silent
# truffle
RUN npm install -g truffle --silent
# wakes up off-chain verifier
RUN curl --silent --output null http://coin-flip-api.herokuapp.com/flip?claim=1
This diff is collapsed.
# FIN4Contracts
These are the smart contracts of the [FIN4Xplorer](https://github.com/FuturICT2/FIN4Xplorer).
# Setup
## Dependencies
```sh
# basics
sudo apt-get install git build-essential python
# node v10
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
source ~/.bashrc
nvm install 10.0.0
nvm use 10.0.0
# on macOS, to prevent gyp related errors
npm explore npm -g -- npm install node-gyp@latest
# truffle
npm install -g truffle
# for local development: ganache-cli or the GUI app from trufflesuite.com/ganache
npm install -g ganache-cli
# project
npm install
```
### Config file
The file `config.json` at root level must be added and filled.
The first two fields are only necessary for non-local deployments and are used in `truffle-config.js`. The account encoded by the mnemonic is paying the deployment costs. Therefore it has to have sufficient funds on the respective network. The *Infura API* key can be obtained by creating a project on infura.io: it is the *Project ID* under *View Project*.
The last two fields define where `truffle` compiles the contract into JSON format and where the deployment info (`Fin4Main` address and the name of the network) will be saved to during deployment. In the example below, these paths are set in a way that assumes this FIN4Contracts repo to be sitting next to the [FIN4Xplorer](https://github.com/FuturICT2/FIN4Xplorer) repo containing the frontend react app. There these files are required for running the app. If you don't build these contracts/addresses files directly there but want to run the frontend, you must manually make sure to place them where the frontend expects them.
```json
{
"MNEMONIC": "",
"INFURA_API_KEY": "",
"CONTRACTS_BUILD_DIRECTORY": "../FIN4Xplorer/src/build/contracts",
"DEPLOYMENT_INFO_SAVING_LOCATION": "../../FIN4Xplorer/src/config"
}
```
## Deployment
To deploy the smart contracts to a local Ganache instance, run:
```sh
truffle migrate
```
To deploy to the Rinkeby testnet, use:
```sh
truffle migrate --network rinkeby
```
truffle compile
truffle migrate --reset --network docker
{
"MNEMONIC": "",
"INFURA_API_KEY": "",
"CONTRACTS_BUILD_DIRECTORY": "./FIN4Xplorer/src/build/contracts",
"DEPLOYMENT_INFO_SAVING_LOCATION": "../FIN4Xplorer/src/config"
}
\ No newline at end of file
pragma solidity ^0.5.17;
import 'contracts/Fin4Token.sol';
import 'contracts/Fin4SystemParameters.sol';
import 'contracts/stub/MintingStub.sol';
import "contracts/verifiers/Fin4BaseVerifierType.sol";
contract Fin4Claiming {
event ClaimSubmitted(address tokenAddr, uint claimId, address claimer, uint quantity, uint claimCreationTime,
string comment, address[] requiredVerifierTypes);
event ClaimApproved(address tokenAddr, uint claimId, address claimer, uint mintedQuantity, uint256 newBalance);
event ClaimRejected(address tokenAddr, uint claimId, address claimer);
event VerifierApproved(address tokenAddrToReceiveVerifierNotice, address verifierTypeAddress, uint claimId,
address claimer, string message);
event VerifierRejected(address tokenAddrToReceiveVerifierNotice, address verifierTypeAddress, uint claimId,
address claimer, string message);
event UpdatedTotalSupply(address tokenAddr, uint256 totalSupply);
/* If we go for the DNS pattern of this contract as Mark suggested #ConceptualDecision
struct ClaimRef {
address token;
uint claimId;
}
mapping (string => ClaimRef) public claimRefs; */
address public creator;
address public Fin4SystemParametersAddress;
address public Fin4ReputationAddress;
constructor(address Fin4SystemParametersAddr) public {
creator = msg.sender;
Fin4SystemParametersAddress = Fin4SystemParametersAddr;
}
function setFin4ReputationAddress(address Fin4ReputationAddr) public {
require(msg.sender == creator, "Only the creator of this smart contract can call this function");
Fin4ReputationAddress = Fin4ReputationAddr;
}
function submitClaim(address tokenAddress, uint variableAmount, string memory comment) public {
uint claimId;
address[] memory requiredVerifierTypes;
uint claimCreationTime;
uint quantity;
(claimId, requiredVerifierTypes, claimCreationTime, quantity) = Fin4Token(tokenAddress)
.submitClaim(msg.sender, variableAmount, comment);
if (!userClaimedOnThisTokenAlready(msg.sender, tokenAddress)) {
tokensWhereUserHasClaims[msg.sender].push(tokenAddress);
}
emit ClaimSubmitted(tokenAddress, claimId, msg.sender, quantity, claimCreationTime, comment, requiredVerifierTypes);
for (uint i = 0; i < requiredVerifierTypes.length; i++) {
if (Fin4BaseVerifierType(requiredVerifierTypes[i]).isNoninteractive()) {
Fin4BaseVerifierType(requiredVerifierTypes[i]).autoCheck(msg.sender, tokenAddress, claimId);
}
}
// Only auto-init applicable verifier types if the claim didn't already got automatically rejected from a constraint in the previous loop
if (!Fin4Token(tokenAddress).claimGotRejected(claimId)) {
// auto-init claims where user would only press an "init verifier" button without having to supply more info
for (uint i = 0; i < requiredVerifierTypes.length; i++) {
// TODO instead of two calls, make .autoSubmitProofIfApplicable()?
if (Fin4BaseVerifierType(requiredVerifierTypes[i]).isAutoInitiable()) {
Fin4BaseVerifierType(requiredVerifierTypes[i]).autoSubmitProof(msg.sender, tokenAddress, claimId);
}
}
}
}
function verifierApprovalPingback(address tokenAddrToReceiveVerifierNotice, address verifierTypeAddress, uint claimId,
address claimer, string memory message) public {
emit VerifierApproved(tokenAddrToReceiveVerifierNotice, verifierTypeAddress, claimId, claimer, message);
}
function verifierRejectionPingback(address tokenAddrToReceiveVerifierNotice, address verifierTypeAddress, uint claimId,
address claimer, string memory message) public {
emit VerifierRejected(tokenAddrToReceiveVerifierNotice, verifierTypeAddress, claimId, claimer, message);
}
// called from Fin4TokenBase
function claimApprovedPingback(address tokenAddress, address claimer, uint claimId, uint quantity, bool canMint) public {
// TODO require...
if (canMint) {
// TODO verify this makes sense and msg.sender is the token
MintingStub(tokenAddress).mint(claimer, quantity);
// can changes to totalSupply happen at other places too though? Definitely if we use the
// ERC20Plus contract with burning for instance... #ConceptualDecision
emit UpdatedTotalSupply(tokenAddress, Fin4Token(tokenAddress).totalSupply());
}
// listen to this event if you provide your own minting policy
emit ClaimApproved(tokenAddress, claimId, claimer, quantity, Fin4Token(tokenAddress).balanceOf(claimer));
// REP reward for a successful claim
MintingStub(Fin4ReputationAddress).mint(claimer, Fin4SystemParameters(Fin4SystemParametersAddress).REPforTokenClaim());
}
function claimRejectionPingback(address tokenAddress, uint claimId, address claimer) public {
emit ClaimRejected(tokenAddress, claimId, claimer);
}
// ------------------------- TOKENS WHERE USER HAS CLAIMS -------------------------
// to keep track on which tokens the user has claims (independent of their approval-statuses)
mapping (address => address[]) public tokensWhereUserHasClaims; // key = user, value = token addresses
function userClaimedOnThisTokenAlready(address user, address tokenAddress) private view returns (bool) {
for (uint i = 0; i < tokensWhereUserHasClaims[user].length; i++) {
if (tokensWhereUserHasClaims[user][i] == tokenAddress) {
return true;
}
}
return false;
}
// used in PreviousClaims
function getTokensWhereUserHasClaims() public view returns(address[] memory) {
return tokensWhereUserHasClaims[msg.sender];
}
// ------------------------- CLAIMS -------------------------
function getMyClaimIdsOnThisToken(address token) public view returns(uint[] memory) {
return Fin4Token(token).getClaimIds(msg.sender);
}
function getClaimOnThisToken(address token, uint claimId) public view
returns(address, bool, bool, uint, uint, string memory, address[] memory, uint[] memory, address[] memory) {
return Fin4Token(token).getClaim(claimId);
}
function getVerifierMessageOnClaim(address token, uint claimId, address verifierAddress) public view returns(string memory) {
return Fin4Token(token).getVerifierMessageOnClaim(claimId, verifierAddress);
}
}
pragma solidity ^0.5.17;
import "solidity-util/lib/Strings.sol";
import "contracts/Fin4Groups.sol";
contract Fin4Collections {
using Strings for string;
address public Fin4GroupsAddress;
function setFin4GroupsAddress(address Fin4GroupsAddr) public {
Fin4GroupsAddress = Fin4GroupsAddr;
}
struct Collection {
uint collectionId;
address creator;
uint adminGroupId;
bool adminGroupIsSet;
address[] tokens;
mapping (address => bool) tokensSet;
string name;
string identifier;
string description;
string color; // not used yet
string logoURL; // not used yet
}
uint public nextCollectionId = 0;
uint private INVALID_INDEX = 9999;
mapping (uint => Collection) public collections;
mapping (string => bool) public identifiers;
function getCollectionsCount() public view returns(uint) {
return nextCollectionId;
}
function createCollection(string memory name, string memory identifier, string memory description) public returns(uint) {
// TODO also check for alphanumeric? How?
require(!identifiers[identifier], "Identifier already in use");
require(identifier.length() > 2, "Identifier is too short"); // TODO #ConceptualDecision
Collection storage col = collections[nextCollectionId];
col.creator = msg.sender;
col.name = name;
col.identifier = identifier;
col.description = description;
col.adminGroupIsSet = false;
identifiers[identifier] = true;
nextCollectionId ++;
return nextCollectionId - 1;
}
function getCollection(uint collectionId) public view returns(bool, bool, bool, uint, address[] memory,
string memory, string memory, string memory) {
Collection memory col = collections[collectionId];
return(col.creator == msg.sender, _userIsAdmin(collectionId, msg.sender), col.adminGroupIsSet, col.adminGroupId,
col.tokens, col.name, col.identifier, col.description);
}
modifier userIsCreator(uint collectionId) {
require(collections[collectionId].creator == msg.sender, "User is not collection creator");
_;
}
modifier userIsAdmin(uint collectionId) {
require(_userIsAdmin(collectionId, msg.sender), "User is not creator or in the appointed admin group");
_;
}
function _userIsAdmin(uint collectionId, address user) public view returns(bool) {
if (collections[collectionId].creator == user) {
return true;
}
if (collections[collectionId].adminGroupIsSet) {
return Fin4Groups(Fin4GroupsAddress).isMember(collections[collectionId].adminGroupId, user);
}
return false;
}
function transferOwnership(uint collectionId, address newOwner) public userIsCreator(collectionId) {
collections[collectionId].creator = newOwner;
}
function setAdminGroupId(uint collectionId, uint groupId) public userIsCreator(collectionId) {
require(Fin4Groups(Fin4GroupsAddress).groupExists(groupId), "Group does not exist");
collections[collectionId].adminGroupId = groupId;
collections[collectionId].adminGroupIsSet = true;
}
function removeAdminGroup(uint collectionId) public userIsCreator(collectionId) {
collections[collectionId].adminGroupIsSet = false;
}
function addTokens(uint collectionId, address[] memory newTokens) public userIsAdmin(collectionId) {
Collection storage col = collections[collectionId];
for (uint i = 0; i < newTokens.length; i ++) {
if (!col.tokensSet[newTokens[i]]) {
col.tokens.push(newTokens[i]);
col.tokensSet[newTokens[i]] = true;
}
}
}
function removeToken(uint collectionId, address tokenToRemove) public userIsAdmin(collectionId) {
Collection storage col = collections[collectionId];
require(col.tokensSet[tokenToRemove], "Token not contained in this collection, can't remove it");
uint tokenIndex = getIndexOfToken(collectionId, tokenToRemove);
uint length = col.tokens.length;
col.tokens[tokenIndex] = col.tokens[length - 1];
delete col.tokens[length - 1];
col.tokens.length --;
col.tokensSet[tokenToRemove] = false;
}
// HELPER METHODS
function getIndexOfToken(uint collectionId, address token) private view returns(uint) {
Collection memory col = collections[collectionId];
for (uint i = 0; i < col.tokens.length; i ++) {
if (col.tokens[i] == token) {
return i;
}
}
return INVALID_INDEX;
}
}
pragma solidity ^0.5.17;
import 'contracts/Fin4Messaging.sol';
import "contracts/util/utils.sol";
contract Fin4Groups is utils {
address public Fin4MessagingAddress;
constructor(address Fin4MessagingAddr) public {
Fin4MessagingAddress = Fin4MessagingAddr;
}
struct Group {
uint groupId;
address creator;
address[] members;
mapping(address => bool) membersSet;
string name;
}
uint public nextGroupId = 0;
uint private INVALID_INDEX = 9999;
mapping (uint => Group) public groups;
function groupExists(uint groupId) public view returns(bool) {
return groupId < nextGroupId; // in the future, consider group deletion? #ConceptualDecision
}
function DeleteGroup(uint groupId) public{
delete groups[groupId];
}
modifier userIsCreator(uint groupId) {
require(groups[groupId].creator == msg.sender, "User is not group creator");
_;
}
function transferOwnership(uint groupId, address newOwner) public userIsCreator(groupId) {
groups[groupId].creator = newOwner;
}
function getGroup(uint groupId) public view returns(address, address[] memory, string memory) {
return (groups[groupId].creator, groups[groupId].members, groups[groupId].name);
}
function getGroupNameAndCreator(uint groupId) public view returns(string memory, address) {
return (groups[groupId].name, groups[groupId].creator);
}
function getGroupsInfo() public view returns(bool[] memory, bool[] memory) {
bool[] memory userIsCreatorArr = new bool[](nextGroupId);
bool[] memory userIsMemberArr = new bool[](nextGroupId);
for (uint i = 0; i < nextGroupId; i ++) {
userIsCreatorArr[i] = groups[i].creator == msg.sender;
userIsMemberArr[i] = groups[i].membersSet[msg.sender];
}
return (userIsCreatorArr, userIsMemberArr);
}
function createGroup(string memory name, bool addCreatorAsMember) public returns(uint) {
Group storage group = groups[nextGroupId];
group.creator = msg.sender;
if (addCreatorAsMember) {
group.members.push(msg.sender);
group.membersSet[msg.sender] = true;
}
group.name = name;
nextGroupId ++;
return nextGroupId - 1;
}
function createGroup2(string memory name) public view returns(uint) {
// Group storage group = groups[nextGroupId];
// group.creator = msg.sender;
// group.name = name;
// nextGroupId ++;
// return nextGroupId - 1;
return 0;
}
function addMembers(uint groupId, address[] memory newMembers) public userIsCreator(groupId) {
for (uint i = 0; i < newMembers.length; i ++) {
groups[groupId].members.push(newMembers[i]);
groups[groupId].membersSet[newMembers[i]] = true;
}
}
function removeMember(uint groupId, address memberToRemove, bool notifyOwner) public {
require(groups[groupId].creator == msg.sender || msg.sender == memberToRemove,
"User is not group creator or removes himself");
require(groups[groupId].membersSet[memberToRemove], "Given address is not a member in this group, can't remove it");
groups[groupId].membersSet[memberToRemove] = false;
uint index = getIndexOfMember(groupId, memberToRemove);
uint length = groups[groupId].members.length;
// overwrite the deletion candidate with the last element
groups[groupId].members[index] = groups[groupId].members[length - 1];
// then delete the last element, via https://ethereum.stackexchange.com/a/1528/56047
delete groups[groupId].members[length - 1];
groups[groupId].members.length --; // via https://stackoverflow.com/a/51171477/2474159
if (notifyOwner) {
string memory message = string(abi.encodePacked("User ", addressToString(memberToRemove),
" has left your group ", groups[groupId].name, " (ID: ", uint2str(groupId), ")."));
Fin4Messaging(Fin4MessagingAddress).addInfoMessage(address(this), groups[groupId].creator, message);
}
}
function getIndexOfMember(uint groupId, address member) public view returns(uint) {
Group memory group = groups[groupId];
for (uint i = 0; i < group.members.length; i ++) {
if (group.members[i] == member) {
return i;
}
}
return INVALID_INDEX;
}
function isMember(uint groupId, address memberToCheck) public view returns(bool) {
return groups[groupId].membersSet[memberToCheck];
}
// can contain zero addresses
function getGroupMembers(uint groupId) public view returns(address[] memory) {
return groups[groupId].members;
}
// used by the Black- and Whitelisting constraint verifier types
function userIsInOneOfTheseGroups(uint[] memory groupIds, address user) public view returns(bool) {
for (uint i = 0; i < groupIds.length; i ++) {
if (isMember(groupIds[i], user)) {
return true;
}
}
return false;
}
}
/*
Copyright (C) 2018-2019 Chair of Computational Social Science, ETH Zürich <http://coss.ethz.ch>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
pragma solidity ^0.5.17;
contract Fin4Main {
address public Fin4MainCreator;
constructor() public {
Fin4MainCreator = msg.sender;
}
address public Fin4UncappedTokenCreatorAddress;
address public Fin4CappedTokenCreatorAddress;
address public Fin4TokenManagementAddress;
address public Fin4ClaimingAddress;
address public Fin4CollectionsAddress;
address public Fin4MessagingAddress;
address public Fin4VerifyingAddress;
address public Fin4GroupsAddress;
address public Fin4SystemParametersAddress;
address public Fin4VotingAddress;
function setSatelliteAddresses(address uncappedTokenCreator, address cappedTokenCreator, address tokenManagement, address claiming,
address collections, address messaging, address verifying, address groups, address systemParameters, address fin4voting) public {