From 246f2dde31fa46dbf0032888afb5bf09bddf22e5 Mon Sep 17 00:00:00 2001
From: Robert Martin-Legene <robert@martin-legene.dk>
Date: Sat, 7 Nov 2020 09:29:45 -0300
Subject: [PATCH] Major rewrite

---
 destileria2.sol | 501 +++++++++++++++++++++++++++++-------------------
 1 file changed, 300 insertions(+), 201 deletions(-)

diff --git a/destileria2.sol b/destileria2.sol
index 19b0954..89aa3ec 100644
--- a/destileria2.sol
+++ b/destileria2.sol
@@ -1,3 +1,7 @@
+// vim:filetype=javascript
+
+// SPDX-License-Identifier: GPL-2.0-or-later
+
 /*
 Copyright 2020 de la Dirección General de Sistemas Informáticos – Secretaría Legal y Técnica - Nación.
 
@@ -9,231 +13,326 @@ You should have received a copy of the GNU General Public License along with thi
 */
 pragma solidity >0.4;
 
+contract destileria2
+{
+    enum UserType { UserOwner, UserDistributor, UserBeneficiary, UserNotFound };
+    // no hace falte 'indexed' ya que solamente va a haber
+    // un evento de este tipo
+    event deployed(address creator);
+    // Cuando un usuario / una distribuidora fue agregado al registro
+    event added(address indexed orderGiver, address indexed victim, UserType usertype, uint256 limit);
+    // Cuando un usuario fue sacado del registro
+    event kicked(address indexed orderGiver, address indexed victim);
+    event limitChanged(address indexed orderGiver, address indexed victim, uint256 topuplimit);
+    event replenished(address indexed orderGiver, address indexed victim, uint256 amountGiven);
+
+    struct UserEntry {
+        address     addr;
+        UserType    userType;
+        uint256     topUpLimit;
+        bool        deleted;
+    }
+
+    address public      owner;
+    mapping(address => uint256)
+            public      addressToUserEntryIndex;
+    UserEntry[] public  userIndex;
+    // Used for reclaiming unused positions in the userIndex
+    uint256[] public    deletedEntries;
+    // Last time we did replenishAll, where did we stop?
+    uint256             allstopmarker = 0;
 
-contract PeronEther {
-    
-    event addedBeneficiary(address indexed distr, address indexed added, uint256 limit);
-    event addedDistributor(address indexed added, uint256 top);
-    event kickedBeneficiary(address indexed distr, address indexed kicked);
-    event kickedDistributor(address indexed kicked);
-    event limitChanged(address indexed distr, address indexed ben, uint256 newLimit);
-    event replenished(address indexed distr, uint256 amount);
-    
-    address     owner;
-    itmap    beneficiaries;
-    itmap    distributors;
-
-    constructor() payable public {
+    constructor() payable public
+    {
         owner = msg.sender;
+        // Because of the problems of using index 0 for real data
+        // I initially set it to just be a dummy (empty) entry.
+        // But we actually need an "empty" object to be returned
+        // when no matches are found, when people search for something
+        // which does not exist. I created an object that I could return
+        // in those cases and stored it in a separate variable, but
+        // then I realized that this is perfect for storing in the
+        // userIndex, because it is of that type. Oh, and index 0
+        // is empty.. can store it there! Oh, and when we search
+        // for a key in the mapping and there's no match, it returns
+        // zero. It seems like it is ideal for this job and will even
+        // make the code smaller
+        // Make first entry always empty (index 0)
+        userIndex.push(UserEntry({
+            // This entry can be returned when no matches were found
+            addr: "0x0000000000000000000000000000000000000000",
+            userType: UserNotFound,
+            topUpLimit: 0,
+            deleted: true
+        }));
     }
-    modifier onlyOwner {
-        require( msg.sender == owner );
+
+    receive() external payable {}
+
+    modifier onlyOwner
+    {
+        require(msg.sender == owner);
         _;
     }
-    modifier onlyDistributor {
-        require (contains(distributors, msg.sender) || msg.sender == owner);
+
+    modifier onlyDistributorOrOwner
+    {
+        require(contains(UserDistributor, msg.sender) || msg.sender == owner);
         _;
     }
-    
-    function hash(string key) public pure returns(bytes32){
-        return keccak256(abi.encodePacked(key));
+
+    function addBeneficiary(address addr, uint256 topuplimit) public onlyDistributorOrOwner returns(bool)
+    {
+        if (topuplimit == 0)
+            return false;
+        // User must not exist already
+        if (getUserType(addr) != UserNotFound)
+            return false;
+        if (topuplimit > getTopUpLimit(UserDistributor, msg.sender)
+            return false;
+        emit added(msg.sender, addr, UserBeneficiary, topuplimit);
+        return !insert(UserBeneficiary, addr, topuplimit);
     }
-    
-    function addBeneficiary(address acc, uint256 limit) public onlyDistributor returns(bool){
-        if (!contains(distributors, acc) && limit < get(distributors, msg.sender)){
-            emit addedBeneficiary(msg.sender, acc, limit);
-            return !insert(beneficiaries, acc, limit);    
-        }
-        return false;
+
+    function addDistributor(address addr, uint256 topuplimit) public onlyOwner returns(bool)
+    {
+        if (topuplimit == 0)
+            return false;
+        // User must not exist already
+        if (getUserType(addr) != UserNotFound)
+            return false;
+        emit added(msg.sender, addr, UserDistributor, topuplimit);
+        return !insert(UserDistributor, addr, topuplimit);
     }
-    
-    function addDistributor(address acc, uint256 top) public onlyOwner returns(bool){
-        if (!contains(beneficiaries, acc)){
-            emit addedDistributor(acc, top);
-            return !insert(distributors, acc, top);    
-        }
-        return false;
+
+    function kick(UserType victimtype, address addr) private returns(bool)
+    {
+        if (!contains(victimtype, addr))
+            return false;
+        emit kicked(msg,sender, addr);
+        return remove(victimtype, addr);
     }
-    
-    function kickDistributor(address acc) public onlyOwner returns(bool){
-        if (contains(distributors, acc)){
-            emit kickedDistributor(acc);
-            return remove(distributors, acc);
-        }
-        return false;
+
+    function kickDistributor(address addr) public onlyOwner returns(bool)
+    {
+        return kick(UserDistributor, addr);
     }
-    
-    function kickBeneficiary(address acc) public onlyOwner returns(bool){
-        if (contains(distributors, acc)){
-            emit kickedBeneficiary(msg.sender, acc);
-            return remove(distributors, acc);
-        }
-        return false;
+
+    function kickBeneficiary(address addr) public onlyDistributorOrOwner returns(bool)
+    {
+        return kick(UserBeneficiary, addr);
     }
-    
-    function changeLimit(address acc, uint256 limit) public onlyDistributor returns(bool){
-        if (contains(beneficiaries, acc) && get(distributors, msg.sender) > limit){
-            modify(beneficiaries, acc, limit);
-            emit limitChanged(msg.sender, acc, limit);
-            return true;
+
+    function changeLimit(address addr, uint256 topuplimit) public onlyDistributorOrOwner returns(bool)
+    {
+        uint256 pos = addressToUserEntryIndex[addr];
+        if (pos == 0)
+            return false;
+        UserEntry entry = userIndex[pos];
+        if (entry.userType != UserDistributor && entry.userType != UserBeneficiary)
+            return false;
+        if (msg.sender != owner)
+        {
+            // Only our owner can change limits for Distributors
+            if (entry.userType == UserDistributor)
+                return false;
+            // Distributors must obey the limit
+            if (topuplimit > getTopUpLimit(UserDistributor, msg.sender)
+                return false;
         }
-        return false;
+        // all is fine - now we change the limit
+        userIndex[pos].topUpLimit = topuplimit;
+        emit limitChanged(msg.sender, addr, topuplimit);
+        return true;
     }
-    
-    function replenish(address acc, uint256 amount) public payable onlyDistributor returns(bool){
-        if(contains(beneficiaries,acc) 
-            && acc.balance < get(beneficiaries,acc)
-            && address(this).balance > amount){
-            acc.transfer(amount);
-            emit replenished(msg.sender, amount);
-            return true;
-        }
-        return false;
+
+    function replenish(address victim, uint256 amount) public payable onlyDistributorOrOwner returns(bool)
+    {
+        if (getUserType(victim) != UserBeneficiary)
+            return false;
+        uint256 topuplimit = getTopUpLimit(UserBeneficiary, victim);
+        // Does the destination account have less than his limit?
+        if (victim.balance + amount > topuplimit)
+            return false;
+        // avoid uint256 overflow
+        if (victim.balance + amount < victim.balance)
+            return false;
+        // Do we have enough ether to send?
+        if (address(this).balance < amount)
+            return false;
+        // Do the transfer and emit an event
+        address payable addrpayable = address(uint160(victim))
+        addrpayable.transfer(amount);
+        emit replenished(msg.sender, victim, amount);
+        return true;
     }
-    
-    function replenishList(address[] accs, uint256[] amounts) public payable{
+
+    // To use this function, you should set gasLimit to at least gasEstimate + 50000
+    function replenishList(address[] accs, uint256[] amounts) public payable
+    {
         require(accs.length == amounts.length);
         uint256 i = 0;
-        if (contractHasEnoughEtherForThese(accs)){
-            for( ;i< accs.length; i++){
-                accs[i].transfer(get(beneficiaries, accs[i]) - accs[i].balance);
-            }    
+        while (i < accs.length && gasleft()>49999)
+        {
+            // we don't like overflow
+            if (accs[i].balance + amounts[i] < accs[i].balance)
+                continue;
+            // Don't allow amounts which will make the balance go above the top up limit.
+            if (accs[i].balance + amounts[i] < getTopUpLimit(UserBeneficiary, accs[i]))
+                replenish(accs[i], amounts[i]);
+            i++;
         }
     }
-    
-    function replenishAll() public payable onlyDistributor returns(bool){
-        if (contractHasEnoughEtherForAll()){
-            uint256 i = iterate_start(beneficiaries);
-            for (; iterate_valid(beneficiaries, i); i = iterate_next(beneficiaries, i)){
-                var (cuenta, top) = iterate_get(beneficiaries, i);
-                if(top > cuenta.balance){
-                    cuenta.transfer(top - cuenta.balance);
-                }
-            }
-            return true;
+
+    // Pay to as many as possible, until we run out of ether (or run through the list
+    function replenishAll() public payable onlyDistributorOrOwner returns(bool)
+    {
+        if (userIndex.length == 0)
+            return false;
+        // The index may have shrunk under the stopmarker
+        if (allstopmarker >= userIndex.length)
+            allstopmarker = 0;
+        // The distributor's topuplimit (for owner this is ignored)
+        uint256 distlimit = getTopUpLimit(msg.sender);
+        uint256 i = allstopmarker;
+        while (++i != allstopmarker)
+        {
+            // If we get to the end of the list, loop back to the start of it
+            if (i >= userIndex.length)
+                i = 0;
+            entry = userIndex[i];
+            // make sure we skip deleted entries
+            if (entry.deleted)
+                continue;
+            // We only handle Beneficiaries
+            if (entry.userType != UserBeneficiary)
+                continue;
+            //
+            if (msg.sender == owner || entry.topUpLimit <= distlimit)
+                topuplimit = entry.topUpLimit;
+            else
+                topuplimit = distlimit;
+            // Does the user actually need to get topped up?
+            if (topuplimit > entry.key.balance)
+                continue;
+            // What's the difference?
+            uint256 amount = topuplimit - entry.key.balance;
+            replenish(entry.key, amount);
+            // Do we have enough ether to send?
+            if (address(this).balance < amount)
+                continue
+            // Do the transfer and emit an event
+            address payable addrpayable = address(uint160(entry.key))
+            addrpayable.transfer(amount);
+            emit replenished(msg.sender, entry.key, amount);
         }
-        return false;
+        return true;
     }
-    
-    function contractHasEnoughEtherForThese(address[] accs) private view returns(bool){
-        uint256 i = 0;
-        uint256 sum = 0;
-        for( ;i< accs.length; i++){
-            uint256 top = get(beneficiaries, accs[i]);
-            if ( top > accs[i].balance){
-                sum += top - accs[i].balance;
-            }
+
+    function getDistributorLimit(address addr) public view returns(uint256)
+    {
+        return getTopUpLimit(UserDistributor, addr);
+    }
+
+    function getBeneficiaryLimit(address addr) public view returns(uint256)
+    {
+        return getTopUpLimit(UserBeneficiary, addr);
+    }
+
+    function getBeneficiariesCount() public view returns(uint256 count)
+    {
+        uint256 count = 0;
+        uint256 i = userIndex.length;
+        while (i-- > 0)
+        {
+            if (userIndex[i].deleted)
+                continue
+            if (userIndex[i].userType == UserBeneficiary)
+                count++
         }
-        return sum < address(this).balance;
-    }
-    
-    function contractHasEnoughEtherForAll() private view returns(bool){
-        uint256 i = iterate_start(beneficiaries);
-        uint256 sum = 0;
-        for (; iterate_valid(beneficiaries, i); i = iterate_next(beneficiaries, i)){
-            var (cuenta, top) = iterate_get(beneficiaries, i);
-            if(top > cuenta.balance){
-                sum += top - cuenta.balance;    
-            }
+    }
+
+    function isBeneficiary(address addr) public view returns(bool)
+    {
+        return contains(UserBeneficiary, addr);
+    }
+
+    function isDistributor(address addr) public view returns(bool)
+    {
+        return contains(UserDistributor, addr);
+    }
+
+    function insert(UserType victimtype, address addr, uint256 value) private returns(bool replaced)
+    {
+        uint256 pos = addressToUserEntryIndex[addr];
+        if(pos > 0 && !userIndex[pos].deleted)
+        {
+            userIndex[pos].topUpLimit = value;
+            self.position[addr].value = value;
+            return true;
         }
-        return sum < address(this).balance;
-    }
-    
-    function getDistributorLimit(address acc) public view returns(uint256){
-        return get(distributors, acc);
-    }
-    
-    function getBeneficiaryLimit(address acc) public view returns(uint256){
-        return get(beneficiaries, acc);
-    }
-    
-    function getBeneficiariesCount() public view returns(uint256){
-        return beneficiaries.size;
-    }
-    
-    function isBeneficiary(address add) public view returns(bool){
-        return contains(beneficiaries, add);
-    }
-    
-    function isDistributor(address add) public view returns(bool){
-        return contains(distributors, add);
-    }
-    
-    // itmap
-    
-    struct itmap{
-        mapping(address => IndexValue) data;
-        KeyFlag[] keys;
-        uint256 size;
-    }
-  
-    struct IndexValue {
-        uint256 keyIndex;
-        uint256 value; 
-    }
-  
-    struct KeyFlag {
-        address key;
-        bool deleted;
-    }
-  
-  function insert(itmap storage self, address key, uint256 value) private returns (bool replaced){
-    uint256 keyIndex = self.data[key].keyIndex;
-    self.data[key].value = value;
-    if (keyIndex > 0){
-        return true;
-    }else{
-        keyIndex = self.keys.length++;
-        self.data[key].keyIndex = keyIndex + 1;
-        self.keys[keyIndex].key = key;
-        self.size++;
+        UserEntry newEntry = UserEntry({
+            addr: addr,
+            userType: victimtype,
+            topUpLimit: value,
+            deleted: false
+        });
+        // recycle deleted entries, if possible
+        if (deletedEntries.length > 0)
+        {
+            pos = deletedEntries[deletedEntries.length-1];
+            deletedEntries.pop();
+            userIndex[pos] = newEntry;
+        }
+        else
+        {
+            pos = userIndex.length;
+            userIndex.push( newEntry );
+        }
+        addressToUserEntryIndex[addr] = pos;
         return false;
     }
-  }
-  
-  function size(itmap storage self) private view returns(uint256){
-      return self.size;
-  }
-  
-  function remove(itmap storage self, address key) private returns (bool success){
-    uint256 keyIndex = self.data[key].keyIndex;
-    if (keyIndex == 0)
+
+    function remove(UserType victimtype, address addr) private returns(bool success)
+    {
+        uint256 pos = addressToUserEntryIndex[addr];
+        if (pos == 0)
+            return false;
+        if(victimtype != userIndex[pos].userType)
+            return false;
+        delete addressToUserEntryIndex[addr];
+        deletedEntries.push(pos);
+        userIndex[pos].deleted = true;
+        return true;
+    }
+
+    function getEntry(address addr) public view returns(UserEntry)
+    {
+        uint256 pos = addressToUserEntryIndex[addr];
+        // if no match, returns the 0th index, which is our "not found" entry
+        return userIndex[pos];
+    }
+
+    function getUserType(address addr) public view returns(UserType)
+    {
+        uint256 pos = addressToUserEntryIndex[addr];
+        // no match will give the 0th userIndex entry which has UserNotFound as UserType
+        return userIndex[pos].userType;
+    }
+
+    function getTopUpLimit(UserType victimtype, address addr) public returns(uint256)
+    {
+        UserEntry uentry = getEntry(addr);
+        if(victimtype == uentry.userType)
+            return uentry.typUpLimit;
+        return 0;
+    }
+
+    function contains(UserType victimtype, address addr) private view returns(bool)
+    {
+        UserEntry uentry = getEntry(addr);
+        if(victimtype == uentry.userType)
+            return true;
         return false;
-    delete self.data[key];
-    self.keys[keyIndex - 1].deleted = true;
-    self.size --;
-    return true;
-  }
-  
-   function modify(itmap storage self, address key, uint256 value) private returns (bool success){
-    uint256 keyIndex = self.data[key].keyIndex;
-    if (keyIndex == 0)
-      return false;
-    self.data[key].value = value;
-    return true;
-  }
-  
-  function get(itmap storage self, address key) private view returns(uint){
-      return self.data[key].value;
-  }
-  
-  function contains(itmap storage self, address key) private view returns (bool){
-    return self.data[key].keyIndex > 0;
-  }
-  function iterate_start(itmap storage self) private view returns (uint keyIndex){
-    return iterate_next(self, uint256(-1));
-  }
-  function iterate_valid(itmap storage self, uint256 keyIndex) private view returns (bool){
-    return keyIndex < self.keys.length;
-  }
-  function iterate_next(itmap storage self, uint256 keyIndex) private view returns (uint256 r_keyIndex){
-    keyIndex++;
-    while (keyIndex < self.keys.length && self.keys[keyIndex].deleted)
-        keyIndex++;
-    return keyIndex;
-  }
-  function iterate_get(itmap storage self, uint256 keyIndex) private view returns (address key, uint256 value){
-    key = self.keys[keyIndex].key;
-    value = self.data[key].value;
-  }
+    }
 }
-- 
GitLab