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.
......@@ -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;
}
}
}
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