Commit 8b2f1dba authored by Anastasios Kalogeropoulos's avatar Anastasios Kalogeropoulos
Browse files

Project added

parents
network/cbdc-net/
network/quorum.sh
tests/node_modules/
[submodule "network/quorum"]
path = network/quorum
url = https://github.com/jpmorganchase/quorum.git
[submodule "network/istanbul-tools"]
path = network/istanbul-tools
url = https://github.com/jpmorganchase/istanbul-tools.git
# DLT4PI-CBDC
![git-header](https://raw.githubusercontent.com/hohmannr/DLT4PI-CBDC/master/pics/git-header.png)
## Documentation
This Repo is structured in main sub-directories `./network`, `./contracts`, `./tests` and `./network/docker`. Each sub-directory has its own README that explains the code setup in this specific sub-directory. In addition to these READMEs, a small Wiki can be found in `./wiki`. It contains all READMEs and additional information.
## Requirements
A generic UNIX operating system is needed.
### Linux
Make sure `linux.h` headers are installed (they normally should be installed already).
### General
```
- python3
- pyyaml
- web3.py
- make
- go
- docker
- solc
- node
- npm
- web3.js
```
**Please make sure the requirements are fullfilled before proceeding.**
### How to install?
The installation process for the dependencies rely heavily on your operating system. Here is some official documentation for each of the dependecies:
**Python3**
Python3 often comes already shipped with your OS. On certain Linux distribution you have to install an additional `python3-dev` package, which contains header-files necessary for running own python code. [This is the offical python3 download page](https://www.python.org/downloads/). See [Testing Disclaimer](#testing-disclaimer) for the python version used.
Additionally to `python3`, you also need the packages `web3.py` and `pyyaml`. The network setup script relies on these dependencies. You can install them via the python3 package installer `pip3` [official pip documentation](https://pypi.org/project/pip/).
```
$ pip3 install web3 pyyaml
```
**make**
Make is needed to make the dependencies. Normally the `make` command ships already with your OS.
**Golang**
Golang is needed to run the quorum/geth instances. How to install it, depends heavily on your OS. Golang normally also gets shipped with your distro's package-manager (if you are on linux). Further information can be found [here](https://golang.org/doc/install).
**Docker**
Docker is the containerization software used to build up the various node types and to form a virtual network on a single physical machine. Docker is sometimes tricky to setup. How to do it properly can be read in their [documentation](https://docs.docker.com/get-docker/). Also make sure that the `docker-daemon` is running before trying to setup the network.
**Solc**
`solc` is the solidity compiler used to automatically compile the smart contracts located in `./contracts`. There are various ways to install `solc` described [here](https://solidity.readthedocs.io/en/v0.4.21/installing-solidity.html). Normally if you install the `solidity` package with your package-manager, it comes with `solc` as its compiler. It is important to have the `solc` command working. Check by typing:
```
$ which solc
```
**Nodejs**
`node` is used for tests performed in `./tests` of the smart contracts aswell as to make interaction with the network nodes easier. `node` can be downloaded from [here](https://nodejs.org/en/download/), but it is most likely also inclueded as a package of your package-manader.
**Node Package Manager**
`npm` is used to build a node packages, e.g. the tests in `./tests`. It comes normally distributed with `node`.
## Where to go now?
Once the requirements are fullfilled, you are ready to setup the network. Therefore please go to `./network` and proceed with the README there.
**Intended Process Flow**
- fullfill requirements
- setup network
- test smart contracts
- connect to network
- build applications upon network
## Testing Disclaimer
The Code has been tested with following versions on `5.6.14-arch1-1` Linux.
```
- python3=3.8.3
- make=4.3
- go=go1.14.3
- docker
- client=19.03.9-ce
- engine=19.03.9-ce
- containerd=v1.3.4.m
- runc=1.0.0-rc10
- docker-init=0.18.0
- solc=0.6.10
- node=v14.5.0
- npm=6.14.5
```
## License
### MIT License
Copyright (c) 2020 fortiss
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
pragma solidity >=0.6.0 <0.7.0;
import "./Governing.sol";
import "./CCBDC.sol";
contract CBDC {
// constants
string public constant name = "General Purpose Coin";
string public constant symbol = "GPC";
uint8 public constant decimals = 18;
// contracts
Governing private governingContract;
CCBDC private ccbdcContract;
// mappings
mapping(address => uint256) public balanceOf;
mapping(address => uint256) public supplyOf;
mapping(address => uint) public isMerchant;
// events
event Transfer(
address from,
address to,
uint256 amount
);
event Minting(
address from,
address to,
uint256 amount
);
event Allocation(
address from,
address to,
uint256 amount
);
event Conversion(
address from,
address to,
uint256 amount
);
// modifiers
modifier onlyGovernor() {
require(governingContract.governors(msg.sender), "Only a governor can call this function.");
_;
}
modifier onlyMaintainer() {
require(governingContract.maintainers(msg.sender), "Only a maintainer can call this function.");
_;
}
modifier onlyCCBDC() {
require(msg.sender == address(ccbdcContract), "Can only be called from CCBDC contract.");
_;
}
modifier onlyBanker() {
require(governingContract.bankers(msg.sender), "Only a governor can call this function.");
_;
}
modifier enoughSupply(uint256 amount) {
require(supplyOf[msg.sender] >= amount);
_;
}
modifier enoughBalance(uint256 amount) {
require(balanceOf[msg.sender] >= amount);
_;
}
// functions
constructor(address _governingContract, address[] memory _bankers, uint[] memory _supplies) public {
governingContract = Governing(_governingContract);
// pre-initialize supply for banker nodes
require(_bankers.length == _supplies.length);
for(uint i = 0; i < _bankers.length; i++) {
address banker = _bankers[i];
require(governingContract.bankers(banker));
supplyOf[banker] = _supplies[i];
}
}
function setup(address _ccbdcContract) public onlyMaintainer {
ccbdcContract = CCBDC(_ccbdcContract);
}
function transfer(address to, uint tokens) public enoughBalance(tokens) {
if(!governingContract.bankers(to)) {
balanceOf[msg.sender] -= tokens;
balanceOf[to] += tokens;
} else {
balanceOf[msg.sender] -= tokens;
supplyOf[to] += tokens;
}
emit Transfer(msg.sender, to, tokens);
}
function mint(address to, uint256 amount) public onlyGovernor {
require(governingContract.bankers(to), "To address must be a banker.");
supplyOf[to] += amount;
emit Minting(msg.sender, to, amount);
}
function allocate(address to, uint256 amount, uint merchantCode) public onlyBanker enoughSupply(amount) {
balanceOf[to] += amount;
supplyOf[msg.sender] -= amount;
isMerchant[to] = merchantCode;
emit Allocation(msg.sender, to, amount);
}
function convert(address from, address to, uint amount) public onlyCCBDC {
if(!governingContract.bankers(to)) {
balanceOf[to] += amount;
} else {
supplyOf[to] += amount;
}
emit Conversion(from, to, amount);
}
}
pragma solidity ^0.6.0;
import "./Governing.sol";
import "./CBDC.sol";
contract CCBDC {
// Defining external contracts
Governing private governingContract;
CBDC private cbdcContract;
// colored coins
mapping(uint => ColoredCoin) public coloredCoins;
uint256 coloredCoinCount;
// requests to receive colored coin
mapping(uint => MintingRequest) public mintingRequests;
uint256 mintingRequestCount;
// check to prevent multiple request for the same coin by the same addr
mapping(address => mapping(uint => bool)) public hasMintingRequest;
// Structs
struct ColoredCoin {
address creator;
uint color;
uint[] shades;
uint256 supply;
uint deadlineBlock;
mapping(address => uint256) balanceOf;
}
struct MintingRequest {
uint coinID;
address sender;
bool approved;
uint256 amount;
}
// Events
event CoinCreation(
address from,
uint coinID,
uint256 supply
);
event Transfer(
uint coinID,
address from,
address to,
uint256 amount
);
event Request(
uint coinID,
address from,
uint256 amount
);
event Approval(
uint coinID,
address to,
uint256 amount
);
event Conversion(
uint coinID,
address from,
address to,
uint256 amount
);
// Modifiers
modifier preDeadline(uint deadLineBlock) {
require(block.number <= deadLineBlock);
_;
}
modifier onlyGovernor() {
require(governingContract.governors(msg.sender));
_;
}
modifier onlyMaintainer() {
require(governingContract.maintainers(msg.sender));
_;
}
modifier isValidCoin(uint coinID) {
// check that coinID exists and that coin has not yet timed out
require(coinID <= coloredCoinCount);
require(coloredCoins[coinID].deadlineBlock > block.number);
_;
}
modifier hasNoRequest(uint coinID) {
require(!hasMintingRequest[msg.sender][coinID], "You have already a request running.");
_;
}
modifier enoughBalance(uint coinID, uint amount) {
require(coloredCoins[coinID].balanceOf[msg.sender] >= amount, "You do not have enough tokens.");
_;
}
// Functions
constructor(address _governingContract) public {
governingContract = Governing(_governingContract);
}
function setup(address _cbdcContract) public onlyMaintainer {
cbdcContract = CBDC(_cbdcContract);
}
// -------------- CCBDC Creation ----------------------------
//STEP 1: Central Bank creates a new coin
function createNewCoin(uint _color, uint[] memory _shades, uint256 _supply, uint _deadline) public onlyGovernor {
// Create nee colored coin
ColoredCoin memory newCC = ColoredCoin({
creator: msg.sender,
color: _color,
shades: _shades,
supply: _supply,
deadlineBlock: block.number + _deadline
});
coloredCoinCount++;
coloredCoins[coloredCoinCount] = newCC;
emit CoinCreation(msg.sender, coloredCoinCount, _supply);
}
function showCoinInfo(uint coinID) public returns(address, uint[] memory, uint256, uint) {
ColoredCoin memory coin = coloredCoins[coinID];
return (coin.creator, coin.shades, coin.supply, coin.deadlineBlock);
}
// -------------- CCBDC Issuance ----------------------------
//STEP 2: User requests specific CCBDC amount with a dedicated minting request
function requestCoin(uint _coinID, uint256 _amount) public isValidCoin(_coinID) hasNoRequest(_coinID) {
MintingRequest memory newMR = MintingRequest({
coinID: _coinID,
sender: msg.sender,
approved: false,
amount: _amount
});
mintingRequestCount++;
mintingRequests[mintingRequestCount] = newMR;
// prevent request spamming by limiting request for specific coinID on one
hasMintingRequest[msg.sender][_coinID] = true;
}
//STEP 3: CCBDC minting requested amount (if approved) and transferring it to the users wallet
function approveMintingRequest(uint requestID) public onlyGovernor {
MintingRequest memory request = mintingRequests[requestID];
// check if request has already been approved and if coin deadline is not exceeded and if there is still enough supply
require(!request.approved, "Request is already approved");
require(coloredCoins[request.coinID].deadlineBlock >= block.number, "Colored Coin has already timed out.");
require(coloredCoins[request.coinID].supply >= request.amount, "Not enough supply left.");
// approve request
mintingRequests[requestID].approved = true;
// update balances
coloredCoins[request.coinID].supply -= request.amount;
coloredCoins[request.coinID].balanceOf[request.sender] += request.amount;
emit Approval(request.coinID, request.sender, request.amount);
}
// --------------- TRADING PHASE! ----------------------------
//STEP 4: user spends CCBDC and if receiver is of same shade (merchant code) as coin, the coin gets transferred to a general purpose CBDC
function transfer(uint coinID, address to, uint tokens) public enoughBalance(coinID, tokens) {
// check if coin and receiver have same shades
bool hasSameShade = false;
for(uint i; i < coloredCoins[coinID].shades.length; i++) {
uint shade = coloredCoins[coinID].shades[i];
if(shade == cbdcContract.isMerchant(to)) {
hasSameShade = true;
}
}
// update balances
coloredCoins[coinID].balanceOf[msg.sender] -= tokens;
if(hasSameShade) {
cbdcContract.convert(msg.sender, to, tokens);
emit Conversion(coinID, msg.sender, to, tokens);
} else {
coloredCoins[coinID].balanceOf[to] += tokens;
emit Transfer(coinID, msg.sender, to, tokens);
}
}
function balanceOf(uint coinID, address query) public returns(uint256) {
return coloredCoins[coinID].balanceOf[query];
}
}
pragma solidity >=0.6.0 <0.7.0;
contract Governing {
mapping(address => bool) public governors;
uint public governorCount;
mapping(address => bool) public maintainers;
uint public maintainerCount;
mapping(address => bool) public observers;
uint public observerCount;
mapping(address => bool) public bankers;
uint public bankerCount;
mapping(address => bool) public blacklist;
uint public blacklistCount;
constructor(address[] memory _governors, address[] memory _maintainers, address[] memory _observers, address[] memory _bankers, address[] memory _blacklist) public {
for(uint i = 0; i < _governors.length; i++) {
governors[_governors[i]] = true;
governorCount++;
}
for(uint i = 0; i < _maintainers.length; i++) {
maintainers[_maintainers[i]] = true;
maintainerCount++;
}
for(uint i = 0; i < _observers.length; i++) {
observers[_observers[i]] = true;
observerCount++;
}
for(uint i = 0; i < _bankers.length; i++) {
bankers[_bankers[i]] = true;
bankerCount++;
}
for(uint i = 0; i < _blacklist.length; i++) {
blacklist[_blacklist[i]] = true;
blacklistCount++;
}
}
enum NodeType {
Governor,
Maintainer,
Observer,
Banker,
Blacklist
}
enum ProposalType {
VoteIn,
VoteOut
}
struct Proposal {
address candidate;
ProposalType t;
NodeType asType;
uint deadlineBlock;
uint voteCount;
uint voteThreshold;
bool accepted;
mapping(address => bool) hasVoted;
}
mapping(uint => Proposal) public proposals;
uint proposalCount;
// Utility functions
function addNode(address node, NodeType t) internal {
if(t == NodeType.Governor) {
governors[node] = true;
governorCount++;
} else if(t == NodeType.Maintainer) {
maintainers[node] = true;
maintainerCount++;
} else if(t == NodeType.Observer) {
observers[node] = true;
observerCount++;
} else if(t == NodeType.Banker) {
bankers[node] = true;
bankerCount++;
} else if(t == NodeType.Blacklist) {
blacklist[node] = true;
blacklistCount++;
}
}
function removeNode(address node, NodeType t) internal {
if(t == NodeType.Governor) {
delete governors[node];
governorCount--;
} else if(t == NodeType.Maintainer) {
delete maintainers[node];
maintainerCount--;
} else if(t == NodeType.Observer) {
delete observers[node];
observerCount--;
} else if(t == NodeType.Banker) {
delete bankers[node];
bankerCount--;
} else if(t == NodeType.Blacklist) {
delete blacklist[node];
blacklistCount--;
}
}
event NewProposal(
uint time,
uint indexed proposalID
);
function makeProposal(address _candidate, NodeType _asType, ProposalType _proposalType) public {
// make sure that only governors can propose
require(governors[msg.sender], "You must be a governor to make a proposal.");
// make sure that address is not already a node
if(_proposalType == ProposalType.VoteIn) {
require(!governors[_candidate] && !observers[_candidate] && !maintainers[_candidate] && !bankers[_candidate], "Candidate cannot be member of any node type.");
} else {
if(_asType == NodeType.Banker) {
require(bankers[_candidate], "Candidate must be node of given type first to be able to be voted out.");
} else if(_asType == NodeType.Governor) {
require(governors[_candidate], "Candidate must be node of given type first to be able to be voted out.");
} else if(_asType == NodeType.Maintainer) {
require(maintainers[_candidate], "Candidate must be node of given type first to be able to be voted out.");
} else if(_asType == NodeType.Observer) {
require(observers[_candidate], "Candidate must be node of given type first to be able to be voted out.");
} else if(_asType == NodeType.Blacklist) {
require(blacklist[_candidate], "Candidate must be node of given type first to be able to be voted out.");
}
}
// create new proposal
Proposal memory proposal = Proposal({
candidate: _candidate,
t: _proposalType,
asType: _asType,
deadlineBlock: block.number + 64,
voteCount: 0,
voteThreshold: governorCount / 2 + 1,