pragma solidity 0.5.12;
contract Ownable{
address public owner;
modifier onlyOwner(){
require(msg.sender == owner);
_; //Continue execution
}
constructor() public{
owner = msg.sender;
}
}
contract Destroyable is Ownable {
function destroy() public onlyOwner {
address payable receiver = msg.sender;
selfdestruct(receiver);
}
}
contract Creatable is Destroyable {
struct Account{
bool created;
string name;
address[] users;
mapping(address => Rights) userRights; // mapping of each user's rights
uint saldo;
}
struct Rights{ // struct containing user rights to:
bool user; // make transactions and withdraw funds
bool manager; // add and delete users
bool creator; // change user rights
}
mapping(address => bytes32[]) internal accountIds; // mapping of all account of each individual user
mapping(bytes32 => Account) internal accounts; // mapping of each account to its id
event accountCreated(bytes32 accId, address creator);
// create an account with one user (creator) and balance equal to value sent with function
function createAccount( string memory name) public payable {
address creator = msg.sender;
bytes32 id = keccak256( abi.encodePacked( name, creator ) ); // create account ID
// verify if this is account already created
require(!accounts[id].created, "You already have an account with that name");
// create temp Account
Account memory newAccount;
newAccount.created = true;
newAccount.name = name; // set account name
address[] memory allUsers = new address[](1); // insert creator's address into users array
allUsers[0] = creator;
newAccount.users = allUsers;
newAccount.saldo = msg.value; // set balance equal to value sent
accounts[id] = newAccount; // insert account to "accounts" mapping by ID
accounts[id].userRights[creator] = Rights(true,true,true); // give all rights to creator
accountIds[creator].push(id); // insert account ID into user's array of IDs
// assert if account was inserted correctly
assert(
keccak256(
abi.encodePacked(
newAccount.created,
newAccount.name,
newAccount.saldo
)
)
==
keccak256(
abi.encodePacked(
accounts[id].created,
accounts[id].name,
accounts[id].saldo
)
)
);
emit accountCreated(id,creator);
}
}
contract Functional is Creatable{
// check if the given accIndex is within the "accountIDs" array
modifier checkAccIndex(uint accIndex){
require(accIndex < accountIds[ msg.sender ].length, "You do not have that many accounts");
_;
}
// check if the given userIndex is within the account's "users" array
modifier checkUserIndex(bytes32 accId, uint userIndex){
require(userIndex < accounts[ accId ].users.length, "There isn't that many users in account");
_;
}
modifier onlyUser(bytes32 accId){
require(accounts[ accId ].userRights[msg.sender].user, "You don't have rights to do this");
_;
}
modifier onlyManager(bytes32 accId){
require(accounts[ accId ].userRights[msg.sender].manager, "You don't have rights to do this");
_;
}
modifier onlyCreator(bytes32 accId){
require(accounts[ accId ].userRights[msg.sender].creator, "You don't have rights to do this");
_;
}
// check if the newUser address is already added to the account
modifier noUser(bytes32 accId, address newUser){
require(!accounts[accId].userRights[newUser].user, "User already added");
_;
}
// returns biggest index of sender's "accountIds" array
function myAccounIdMaxIndex() public view checkAccIndex(0) returns(uint){
return( accountIds[ msg.sender ].length -1 );
}
// return ID of senders account with chosen index
function myAccountId( uint accIndex ) public view checkAccIndex(accIndex) returns( bytes32 ) {
return( accountIds[ msg.sender ][ accIndex ] );
}
// returns biggest index of
function maxUserIndex( bytes32 accId ) public view onlyUser(accId) returns(uint){
return( accounts[ accId ].users.length -1 );
}
// return address of chosen user of one of your accounts
function userAddress( bytes32 accId, uint userIndex ) public view onlyUser(accId) checkUserIndex(accId, userIndex) returns( address user ){
return( accounts[ accId ].users[ userIndex ] );
}
// return user rights
function userRights(bytes32 accId, address user) public view onlyUser(accId) returns (bool, bool, bool){
return(
accounts[accId].userRights[user].user,
accounts[accId].userRights[user].manager,
accounts[accId].userRights[user].creator
);
}
}
contract Manageable is Functional{
event userAdded(bytes32 accId, address newUser);
event userDeleted(bytes32 accId, address deletedUser);
event userRightsChanged(bytes32 accId, address user, bool manager, bool owner);
// add a new user to your account
// new user will receive user rights by default
function addUser( bytes32 accId, address newUser )
public onlyManager(accId) noUser(accId, newUser) {
uint usersCount = accounts[ accId ].users.length;
accounts[ accId ].users.push( newUser ); // add new user to account
accounts[ accId ].userRights[newUser].user = true; // mark new user as created
accountIds[ newUser ].push( accId ); // add account to user's "accountIds" array
assert( accounts[ accId ].users.length == usersCount + 1 );
emit userAdded(accId, newUser);
}
// delete user from your account
// removes also the account from user's "accountIds" array
function deleteUser( bytes32 accId, uint userIndex )
public onlyManager(accId) checkUserIndex(accId, userIndex) {
require(userIndex > 0, "You cannot remove creator");
uint usersCount = accounts[accId].users.length; // get user count
address deletedUser = accounts[accId].users[userIndex]; // get deleted user address
delete accounts[accId].users[userIndex]; // delete user from account
if(userIndex < usersCount -1){ // move all to the left to fill gap
for (uint i=userIndex; i<usersCount-1; i++){
accounts[accId].users[i] = accounts[accId].users[i+1];
}
assert(accounts[accId].users[userIndex] != deletedUser);
}
accounts[accId].users.length--; // decrease length
assert(accounts[accId].users[usersCount-2] != deletedUser);
delete accounts[accId].userRights[deletedUser]; // delete user rights
deleteAccFromUser(deletedUser, accId); // delete account from user's accIds array
emit userDeleted(accId, deletedUser);
}
// remove account from user's "accountIds" array
function deleteAccFromUser(address deletedUser, bytes32 accId) internal {
uint userAccCount = accountIds[deletedUser].length; // get length of user's accIds array
for (uint i=0; i < userAccCount; i++){ // find index of the account
if (accountIds[deletedUser][i] == accId){
if (userAccCount==1){ // if it is the only user's account
delete accountIds[deletedUser]; // delete user from mapping
} else {
delete accountIds[deletedUser][i]; // delete account from user
// move all to the left to fill gap
if(i < userAccCount -1){
for (uint j=i; j<userAccCount -1; j++){
accountIds[deletedUser][j] = accountIds[deletedUser][j+1];
}
assert(accountIds[deletedUser][i] != accId);
}
// update array length
accountIds[deletedUser].length--;
assert(accountIds[deletedUser][ userAccCount-2 ] != accId);
break;
}
}
}
}
// modify user's "manager" and "owner" rights values
function changeUserRights(bytes32 accId, uint userIndex, bool manager, bool creator)
public onlyCreator(accId) {
address user = userAddress(accId, userIndex);
accounts[ accId ].userRights[user].manager = manager;
accounts[ accId ].userRights[user].creator = creator;
emit userRightsChanged(accId, user, manager, creator);
}
}
contract AccountManager is Manageable{
event toppedUp(bytes32 accId, uint amount);
event transferSuccessful(bytes32 senderId, address receiver, uint amount);
event internalTransferSuccessful(bytes32 senderId, bytes32 receiverId, uint amount);
event withdrawalSuccessful(bytes32 senderId, address receiver, uint amount);
modifier nonZeroValue(uint value){
require(value > 0, "Non-zero value required");
_;
}
modifier balanceCheck(bytes32 accId, uint toTransfer){
require(toTransfer <= accounts[ accId ].saldo, "Insufficient funds");
_;
}
function topUp( bytes32 accId ) public payable nonZeroValue(msg.value){
accounts[ accId ].saldo += msg.value;
emit toppedUp(accId, msg.value);
}
function transfer(bytes32 accId, address payable receiver, uint toTransfer)
public onlyUser(accId) nonZeroValue(toTransfer) balanceCheck( accId, toTransfer){
uint oldBalance = accounts[ accId ].saldo;
accounts[ accId ].saldo = oldBalance - toTransfer;
receiver.transfer(toTransfer);
emit transferSuccessful(accId,receiver, toTransfer);
}
function internalTransfer(bytes32 senderAccId, bytes32 receiverAccId, uint toTransfer)
public onlyUser(senderAccId) nonZeroValue(toTransfer) balanceCheck( senderAccId, toTransfer){
uint balanceSum = accounts[ senderAccId ].saldo + accounts[ receiverAccId ].saldo;
accounts[ senderAccId ].saldo -= toTransfer;
accounts[ receiverAccId ].saldo += toTransfer;
assert( balanceSum == accounts[ senderAccId ].saldo + accounts[ receiverAccId ].saldo );
emit internalTransferSuccessful(senderAccId, receiverAccId, toTransfer);
}
function withdraw(bytes32 accId, uint toTransfer)
public onlyUser(accId) nonZeroValue(toTransfer) balanceCheck( accId, toTransfer) {
uint oldBalance = accounts[ accId ].saldo;
accounts[ accId ].saldo = oldBalance - toTransfer;
msg.sender.transfer(toTransfer);
emit withdrawalSuccessful(accId, msg.sender, toTransfer);
}
function accountBalance( bytes32 accId ) public view onlyUser(accId) returns(uint) {
return( accounts[ accId ].saldo );
}
}