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

Project added

MNEMONIC=season prevent fault almost then hungry lazy typical pipe exist recipe milk
\ No newline at end of file
[submodule "FIN4Contracts"]
path = FIN4Contracts
url =
[submodule "FIN4Xplorer"]
path = FIN4Xplorer
url =
[submodule "ethereum-bridge"]
path = ethereum-bridge
url =
# See for more about ignoring files.
# dependencies
# testing
# production
# misc
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 ./
RUN chmod ugo+rwx
RUN npm install --silent
# truffle
RUN npm install -g truffle --silent
# wakes up off-chain verifier
RUN curl --silent --output null
This diff is collapsed.
# FIN4Contracts
These are the smart contracts of the [FIN4Xplorer](
# Setup
## Dependencies
# basics
sudo apt-get install git build-essential python
# node v10
curl -o- | 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
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 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]( 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.
"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:
truffle migrate
To deploy to the Rinkeby testnet, use:
truffle migrate --network rinkeby
truffle compile
truffle migrate --reset --network docker
"CONTRACTS_BUILD_DIRECTORY": "./FIN4Xplorer/src/build/contracts",
\ 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)) {
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; = 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.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.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;
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;
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.membersSet[msg.sender] = true;
} = name;
nextGroupId ++;
return nextGroupId - 1;
function createGroup2(string memory name) public view returns(uint) {
// Group storage group = groups[nextGroupId];
// group.creator = msg.sender;
// = 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].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
delete groups[groupId].members[length - 1];
groups[groupId].members.length --; // via
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;
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 <>
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
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 <>.
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;