Skip to content
Snippets Groups Projects
Commit 246f2dde authored by Robert Martin-Legene's avatar Robert Martin-Legene
Browse files

Major rewrite

parent 409b19cb
No related branches found
No related tags found
No related merge requests found
// 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. 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 ...@@ -9,231 +13,326 @@ You should have received a copy of the GNU General Public License along with thi
*/ */
pragma solidity >0.4; 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 { constructor() payable public
{
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 {
owner = msg.sender; 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){ function addBeneficiary(address addr, uint256 topuplimit) public onlyDistributorOrOwner returns(bool)
return keccak256(abi.encodePacked(key)); {
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){ function addDistributor(address addr, uint256 topuplimit) public onlyOwner returns(bool)
if (!contains(distributors, acc) && limit < get(distributors, msg.sender)){ {
emit addedBeneficiary(msg.sender, acc, limit); if (topuplimit == 0)
return !insert(beneficiaries, acc, limit); return false;
} // User must not exist already
return false; 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){ function kick(UserType victimtype, address addr) private returns(bool)
if (!contains(beneficiaries, acc)){ {
emit addedDistributor(acc, top); if (!contains(victimtype, addr))
return !insert(distributors, acc, top); return false;
} emit kicked(msg,sender, addr);
return false; return remove(victimtype, addr);
} }
function kickDistributor(address acc) public onlyOwner returns(bool){ function kickDistributor(address addr) public onlyOwner returns(bool)
if (contains(distributors, acc)){ {
emit kickedDistributor(acc); return kick(UserDistributor, addr);
return remove(distributors, acc);
}
return false;
} }
function kickBeneficiary(address acc) public onlyOwner returns(bool){ function kickBeneficiary(address addr) public onlyDistributorOrOwner returns(bool)
if (contains(distributors, acc)){ {
emit kickedBeneficiary(msg.sender, acc); return kick(UserBeneficiary, addr);
return remove(distributors, acc);
}
return false;
} }
function changeLimit(address acc, uint256 limit) public onlyDistributor returns(bool){ function changeLimit(address addr, uint256 topuplimit) public onlyDistributorOrOwner returns(bool)
if (contains(beneficiaries, acc) && get(distributors, msg.sender) > limit){ {
modify(beneficiaries, acc, limit); uint256 pos = addressToUserEntryIndex[addr];
emit limitChanged(msg.sender, acc, limit); if (pos == 0)
return true; 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){ function replenish(address victim, uint256 amount) public payable onlyDistributorOrOwner returns(bool)
if(contains(beneficiaries,acc) {
&& acc.balance < get(beneficiaries,acc) if (getUserType(victim) != UserBeneficiary)
&& address(this).balance > amount){ return false;
acc.transfer(amount); uint256 topuplimit = getTopUpLimit(UserBeneficiary, victim);
emit replenished(msg.sender, amount); // Does the destination account have less than his limit?
return true; if (victim.balance + amount > topuplimit)
} return false;
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); require(accs.length == amounts.length);
uint256 i = 0; uint256 i = 0;
if (contractHasEnoughEtherForThese(accs)){ while (i < accs.length && gasleft()>49999)
for( ;i< accs.length; i++){ {
accs[i].transfer(get(beneficiaries, accs[i]) - accs[i].balance); // 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){ // Pay to as many as possible, until we run out of ether (or run through the list
if (contractHasEnoughEtherForAll()){ function replenishAll() public payable onlyDistributorOrOwner returns(bool)
uint256 i = iterate_start(beneficiaries); {
for (; iterate_valid(beneficiaries, i); i = iterate_next(beneficiaries, i)){ if (userIndex.length == 0)
var (cuenta, top) = iterate_get(beneficiaries, i); return false;
if(top > cuenta.balance){ // The index may have shrunk under the stopmarker
cuenta.transfer(top - cuenta.balance); if (allstopmarker >= userIndex.length)
} allstopmarker = 0;
} // The distributor's topuplimit (for owner this is ignored)
return true; 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){ function getDistributorLimit(address addr) public view returns(uint256)
uint256 i = 0; {
uint256 sum = 0; return getTopUpLimit(UserDistributor, addr);
for( ;i< accs.length; i++){ }
uint256 top = get(beneficiaries, accs[i]);
if ( top > accs[i].balance){ function getBeneficiaryLimit(address addr) public view returns(uint256)
sum += top - accs[i].balance; {
} 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 isBeneficiary(address addr) public view returns(bool)
function contractHasEnoughEtherForAll() private view returns(bool){ {
uint256 i = iterate_start(beneficiaries); return contains(UserBeneficiary, addr);
uint256 sum = 0; }
for (; iterate_valid(beneficiaries, i); i = iterate_next(beneficiaries, i)){
var (cuenta, top) = iterate_get(beneficiaries, i); function isDistributor(address addr) public view returns(bool)
if(top > cuenta.balance){ {
sum += top - cuenta.balance; 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; UserEntry newEntry = UserEntry({
} addr: addr,
userType: victimtype,
function getDistributorLimit(address acc) public view returns(uint256){ topUpLimit: value,
return get(distributors, acc); deleted: false
} });
// recycle deleted entries, if possible
function getBeneficiaryLimit(address acc) public view returns(uint256){ if (deletedEntries.length > 0)
return get(beneficiaries, acc); {
} pos = deletedEntries[deletedEntries.length-1];
deletedEntries.pop();
function getBeneficiariesCount() public view returns(uint256){ userIndex[pos] = newEntry;
return beneficiaries.size; }
} else
{
function isBeneficiary(address add) public view returns(bool){ pos = userIndex.length;
return contains(beneficiaries, add); userIndex.push( newEntry );
} }
addressToUserEntryIndex[addr] = pos;
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++;
return false; return false;
} }
}
function remove(UserType victimtype, address addr) private returns(bool success)
function size(itmap storage self) private view returns(uint256){ {
return self.size; uint256 pos = addressToUserEntryIndex[addr];
} if (pos == 0)
return false;
function remove(itmap storage self, address key) private returns (bool success){ if(victimtype != userIndex[pos].userType)
uint256 keyIndex = self.data[key].keyIndex; return false;
if (keyIndex == 0) 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; 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;
}
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment