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