diff --git a/ProofOfExistence.sol b/ProofOfExistence.sol index ee4f8ed179974516b26899a0ee2d770172504486..0fc178aa3d52813e8137dcc7d8517db832007f3d 100644 --- a/ProofOfExistence.sol +++ b/ProofOfExistence.sol @@ -1,29 +1,50 @@ pragma solidity ^0.4.24; -contract ProofOfExistence { +contract Stamper { + event Stamped(address indexed from, string indexed hash, string indexed ots); + + event Deploy(address indexed from); + event SelfDestroy(address indexed from); + struct Dato { uint blockNumber; - uint256 hash; + string hash; } - mapping (uint256 => Dato) private hashstore; + mapping (string => Dato) private hashstore; + address owner; + constructor() public { + owner = msg.sender; + emit Deploy(msg.sender); + } - function stamp(uint256 ots, uint256 file_hash) public { - hashstore[ots] = Dato({blockNumber: block.number, hash: file_hash}); + function stamp(string ots, string file_hash) public { + if (hashstore[ots].blockNumber == 0) { + emit Stamped(msg.sender, file_hash, ots); + hashstore[ots] = Dato({blockNumber: block.number, hash: file_hash}); + } } - function verify(uint256 ots, uint256 file_hash) public view returns(bool){ + function verify(string ots, string file_hash) public view returns(bool){ Dato memory dato = hashstore[ots]; - return dato.hash == file_hash; + return stringsEqual(dato.hash, file_hash); } - function getHash(uint256 ots) public view returns(uint256){ + function getHash(string ots) public view returns(string){ Dato memory dato = hashstore[ots]; return dato.hash; } - function getBlockNumber(uint256 ots) public view returns(uint){ + function getBlockNumber(string ots) public view returns(uint){ Dato memory dato = hashstore[ots]; return dato.blockNumber; } + + function stringsEqual(string memory _a, string memory _b) internal pure returns (bool) { + bytes memory a = bytes(_a); + bytes memory b = bytes(_b); + + if (keccak256(a) != keccak256(b)) { return false; } + return true; + } } \ No newline at end of file diff --git a/README.md b/README.md index c4aa6e79d69a40a9f8de9c10bfeef162806052e3..09b17cda88e52cad36f7bf2876e4d37cabbd0388 100644 --- a/README.md +++ b/README.md @@ -1 +1,107 @@ -Contracts de verify y stamp + +# Contrato de prueba de existencia + +ProofOfExistence.sol + +Este smart contract está desarrollado en solidity + +### Interfaz: + +- Constructor: Define el poseedor del contrato como aquel que deploya el contrato, emite el evento Deploy + +- stamp(string clave, string valor) -> void : el contrato guarda valor y bloque en un diccionario para la clave dada, si la clave ya estaba asignada, no hace nada. Emite el evento Stamped, en el caso que sà se haga la asignación, con los parámetros indexados sobre la clave, el valor y la cuenta que envió la transacción + +- verify(string clave, string valor) -> bool : verifica que para la clave dada, tiene el valor asignado en el diccionario + +- getBlock(string clave) -> unsigned int : precondición: está definido el diccionario, devuelve el int que corresponde al número de bloque donde se hizo el stamp original + +- selfDestroy() -> void : precondición: el usuario es el poseedor del contrato. Invoca el selfdestroy del contrato, y se envÃa todo el eter que tenga al poseedor del contrato, emite el evento SelfDestroy con esos parámetros + + +Notas: + +- Se deberÃa hacer un call del verify antes de hacer una transacción de stamp, para que en el caso de que ya exista, no consumir gas innecesario + + +# Contrato de distribución de ether + +peronEther.sol + +### Sobre la problemática: +Sobre redes con consenso de Proof of Authority, al no tener mecanismo de emisión de token descentralizado, es necesario tener una idea de cómo distribuir el ether. Y esta herramienta, tiene que permitir entregar ether a aquellos que NO tengan capacidad de generarlo, y que además tenga restricciones para, al monitorear abusos, limitar o incluso expulsar a los receptores de ether. + +Además, el otro problema es verificar que las cuentas que corresponden a los beneficiarios, efectivamente están bajo control, o más bien, que sus claves privadas estén asignadas a gente que tenga acceso a ellas, para no entregar ether a claves públicas sin control + +### Funcional: + +La idea de este contrato es tener una herramienta de entrega de ether, con un sistema de autorización sencillo. +Se identifican 3 roles: + +- Beneficiario: un account sobre el que se le va a enviar ether según el criterio del distribuidor + +- Distribuidor: un account que puede asignar, eliminar y modificar topes de beneficiarios, y puede hacer los reabastecimientos + +- Owner: el account que hace el deploy del contrato es quien tiene este rol, y es quien puede agregar, eliminar y modificar topes de distribuidores y beneficiarios + +Cada beneficiario tiene un tope, y si en el momento de reabastecimiento, se le dará la diferencia entre su balance y el tope. Este es asignado por el distribuidor que lo agregó, quien también puede modificarlo en cualquier momento, y a su vez, el tope que puede asignar un distribuidor a un beneficiario no puede ser mayor que su propio tope, que es finalmente decidido por el owner. Esto es para asegurar que los distribuidores no asignan lÃmites de balance demasiado altos a los beneficiarios. +En cualquier momento un distribuidor puede reabastecer a todos, algunos o un beneficiario, si el balance del contrato es suficiente para la acción + +Sobre la verificación de las cuentas, es un paso difÃcil, porque naturalmente la blockchain como tecnologÃa piensa en las cuentas como pares de claves públicas/privadas para desacoplar el hecho de que hay un ser humano detrás con el control de la privada, y por lo tanto de la cuenta y todas sus acciones. Es parte de la problemática entender que la asociación de una cuenta implica que hay alguien que dice tener el control sobre la cuenta a asociar, y que de hecho lo tiene. +Para eso, el contrato tiene un servicio para agregar candidatos, que realiza los siguientes pasos: + +- Agregar un candidato a beneficiario, y el hash de una clave generada según un criterio arbitrario a nivel dapp, aunque esta clave tiene que ser universalmente única, y esa clave es revelada al usuario a nivel dapp +- A ese candidato, se le pasa una cantidad mÃnima de ether +- El candidato invoca al contrato, lo cual puede hacer porque se le habilitó el saldo suficiente, y pasa la clave sin aplicarle el hash al contrato, el contrato hashea la clave y compara con la pasada originalmente, si coincide, el candidato es aprobado + +La decisión de pasar el hash al contrato al principio es porque, como en blockchain todo es público, pasar la clave en ese momento no sirve para distinguir porque cualquiera la puede ver en cualquier momento, por eso el hash. Además en el momento que el candidato haga la verificación, ahà si pasa la clave original, porque si la clave es de hecho la correcta, el contrato la hashea y si coincide entonces ya está aprobado el candidato, y no hay problema con que la clave esté pública ya que no tiene utilidad + +### Interfaz: + +- Constructor: define el poseedor del contrato + +- addDistributor(address distr, unsigned int tope) -> bool : precondición el account es el owner. distr es agregado al listado de distribuidores con tope como su lÃmite de asignación + +- kickDistributor(address distr) -> bool : devuelve True si distr es un distribuidor y el account es el owner, caso contrario devuelve False. Quita a distr del listado + +- addBeneficiary(address ben, unsigned int top) -> bool : devuelve True si ben no es distribuidor ni owner, y el account que está haciendo la transacción sà es distribuidor u owner, tope es menor o igual al tope del account haciendo la transacción, caso contrario devuelve False. Agrega el beneficiario al listado de beneficiarios + +- kickBeneficiary(address ben) -> bool : devuelve True si el beneficiario está en el listado de beneficiarios y el account haciendo la transacción es distribuidor u owner, caso contrario devuelve False. Elimina ben del listado de beneficiarios + +- changeLimit(address ben, unsigned int top) -> bool: devuelve True si ben es beneficiario, el account haciendo la transacción es distribuidor u owner, y tope es menor al tope asignado al distribuidor, caso contrario devuelve False. Cambia el tope del beneficiario + +- replenish(address ben, unsigned int amount) -> bool: devuelve True si ben es beneficiario, el account haciendo la transacción es distribuidor, el balance de ben es menor a su tope y el balance del contrato es mayor a amount, caso contrario devuelve False. Transfiere amount ether a ben + +- getDistributorLimit(address dist) -> unsigned int: devuelve el tope de asignación del distribuidor + +- getBeneficiaryLimit(address ben) -> unsigned int: devuelve el tope a recibir del beneficiario + +- getBeneficiaryCount() -> unsigned int: devuelve la cantidad de beneficiarios + +- addSuitor(address add, string password) -> bool: devuelve true si el contrato tiene suficiente ether para transferir al candidato, y false en caso contrario. NO IMPLEMENTADO + +- proveControl() -> bool: NO IMPLEMENTADO + +Los métodos replenishAll y replenishList tienen funcionalidad similar a la de replenish pero para hacer reabastecimientos masivos + +Notas: + +- Todos los métodos tienen valor de return para poder hacer un call antes de una transacción para verificar su funcionamiento y lograr ahorrar gas + +### Comparando con el SC original + +Aunque conceptualmente, ambos contratos son similares, porque tienen una lista de beneficiarios y un servicio para distribuir el ether, sin embargo presentan las sig diferencias: + +- El original tiene un sistema de permisos más limitado, todo el poder concetrado en un sólo admin, y aunque en este tipo de contextos, donde sistemas de autorización son más difÃciles de definir, este contrato es un poco más flexible, al tener administradores con limites de asignación de tope distintos + +- El método distribute del original, entrega indiscriminadamente la diferencia entre el balance y el tope de cada beneficiario, lo cual puede no ser necesariamente la intención, en el nuevo ademaÅ› de tener este servicio, presenta otros métodos que ofrecen más granularidad, para reabastecer cuentas especÃficas una cantidad de ether permitida que no sea necesariamente la diferencia + +- El nuevo tiene separadas las funcionalidades de setEtherAllowance en varios métodos para facilitar el uso + +- El nuevo, en los métodos que tienen loops implementados, no se hace verificación de gas, porque es algo que se puede estimar, ya que los casos son los sigs: + - Entre invocaciones no se hizo transacción que altere el tamaño de los beneficiarios, entonces al usar estimateGas del lado de geth o web3, es la estimación precisa + - Si se hacen invocaciones que alteran el tamaño de los beneficiarios, y esas transacciones están pendientes, se puede tomar estimar el costo de esas transacciones y agregarlo al de esta + +### TODOs + +- [x] Mecanismo de verificación de cuenta, para verificar que los beneficiarios a agregar son cuentas con clave privada bajo control, todavÃa queda decidir si esto es parte de este smart contract +- [ ] Implementar addSuitor y proveControl, que son los métodos para el mecanismo de verificación de cuenta diff --git a/peronEther.sol b/peronEther.sol index b5cd4c3f5e7c292e010b6f717dc486e5cbb99efb..aab0bb9aef8d253210825e750d1b0bac1bd0eee3 100644 --- a/peronEther.sol +++ b/peronEther.sol @@ -1,45 +1,266 @@ pragma solidity ^0.4.11; -import "iterableMapping.sol"; contract PeronEther { - // Just a struct holding our data. - IterableMapping.itmap data; + 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); + event suitorAdded(address indexed suitor, address indexed distr); + event suitorApproved(address indexed suitor); + event suitorNotApproved(address indexed suitor); + event proofOfControlWeiAmountChanged(uint256 amount); - // Insert something - function addReceiver(address k) public payable returns (uint size){ - // Actually calls itmap_impl.insert, auto-supplying the first parameter for us. - IterableMapping.insert(data, k, 0); - // We can still access members of the struct - but we should take care not to mess with them. - return data.size; - } + address owner; + itmap beneficiaries; + itmap distributors; + mapping(address=>bytes32) suitors; + uint256 proofOfControlWeiAmount; - /* - msg.sender and msg.value are implicitly available, contain information - about the adress of a caller and amount of ether they sent with the call (in wei) - */ - function deposit() public payable returns(bool success) { - uint eth = msg.value / IterableMapping.size(data); - uint i = IterableMapping.iterate_start(data); - for (; IterableMapping.iterate_valid(data, i); i = IterableMapping.iterate_next(data, i)){ - var (cuenta, balance) = IterableMapping.iterate_get(data, i); - balance += eth; - IterableMapping.modify(data,cuenta,balance); + constructor() payable public { + owner = msg.sender; + } + modifier onlyOwner { + require( msg.sender == owner ); + _; + } + modifier onlyDistributor { + require (contains(distributors, msg.sender) || msg.sender == owner); + _; + } + + function addSuitor(address add, bytes32 password) public payable onlyDistributor returns(bool){ + if(address(this).balance >= proofOfControlWeiAmount){ + suitors[add] = password; + emit suitorAdded(add, msg.sender); + add.transfer(proofOfControlWeiAmount); + return true; } - return true; + return false; } - function getBalance(address cuenta) public view returns(uint) { - return IterableMapping.get(data,cuenta); + function proveControl(string key) public returns(bool){ + if(proofOfControlWeiAmount > 0 && this.hash(key)==suitors[msg.sender]){ + emit suitorApproved(msg.sender); + delete suitors[msg.sender]; + return true; + }else{ + emit suitorNotApproved(msg.sender); + return false; + } } - function distribute() public{ - uint i = IterableMapping.iterate_start(data); - for (; IterableMapping.iterate_valid(data, i); i = IterableMapping.iterate_next(data, i)){ - var (cuenta, balance) = IterableMapping.iterate_get(data, i); - cuenta.transfer(balance); - IterableMapping.modify(data,cuenta,0); + function hash(string key) public pure returns(bytes32){ + return keccak256(abi.encodePacked(key)); + } + + 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 acc, uint256 top) public onlyOwner returns(bool){ + if (!contains(beneficiaries, acc)){ + emit addedDistributor(acc, top); + return !insert(distributors, acc, top); } + return false; + } + + function setProofOfControlWeiAmount(uint256 amount) public onlyOwner returns(bool){ + proofOfControlWeiAmount = amount; + emit proofOfControlWeiAmountChanged(amount); + } + + function getProofOfControlWeiAmount() public view returns(uint256){ + return proofOfControlWeiAmount; + } + + function kickDistributor(address acc) public onlyOwner returns(bool){ + if (contains(distributors, acc)){ + emit kickedDistributor(acc); + return remove(distributors, acc); + } + return false; + } + + function kickBeneficiary(address acc) public onlyOwner returns(bool){ + if (contains(distributors, acc)){ + emit kickedBeneficiary(msg.sender, acc); + return remove(distributors, acc); + } + return false; + } + + 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; + } + return false; + } + + 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 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); + } + } + } + + 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; + } + return false; + } + + 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; + } + } + 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; + } + } + 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++; + 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) + 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; + } }