En la lógica de un contrato inteligente pueden ocurrir errores que debemos controlar y actuar en consecuencia ante estos escenarios.
Manejo de errores en Solidity
Solidity permite la declaración de Errores cuya función es similar a la de los eventos, con la diferencia de que estos pueden revertir los cambios de estado hechos. Sin embargo, el gas usado no es devuelto a quien generó la transacción.
La declaración de errores permite dar información más detallada sobre el error que acaba de ocurrir y generar una notificación, similar que los eventos. Los cambios previos en la transacción se revierten, por lo que no debes preocuparte si cambiaste el valor de alguna variable previamente.
// SPDX-License-Identifier: MITpragma solidity >=0.7.0<0.9.0;contract Event { error SendError(string message); function doSomething() public { revert SendError(""Mensaje del error"");}}
La declaración de los errores personalizables se realiza con la palabra clave error y recibe todos los parámetros que el mismo pueda necesitar. Su posterior lanzamiento se realiza con revert e invocando el error a través de su nombre.
Si lanzas un error desde Remix, verás la X roja y la información respectiva al error que acaba de ocurrir.
Combina esa característica de Solidity para desarrollar flujos en el código que te permitan controlar todos los escenarios posibles y actuar en consecuencia. Informarle a los usuarios del proyecto qué está ocurriendo en el contrato te ayudará a mejorar su experiencia de usuario interactuando con una aplicación.
Contribución creada por:Luis Enrique Herrera y Kevin Fiorentino (Platzi Contributors).
Revierte los cambios aplicados para dar conciencia a la ejecución (El valor del gas cobrado por la ejecución no se devuelve a quien llama el contrato)
Gracias de nuevo pana
Muchas gracias
Espero les sirva los comentarios que dejo, se los dejo con amor <3
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.7.0<0.9.0; contract platziProject{//Aqui creamos nuestras variables bool isFundable; uint256 Goal; uint256 totalFunded; address owner; uint256 requiredFunds;//Inicializamoslos valores, hay que recordar que el constructor se ejecuta solo una vez cuando se crea el contratoconstructor(){Goal=0; owner = msg.sender; totalFunded =0; isFundable =true;}//No te preocupes por esto,luego loaprenderemos. El modifier permite cambiar el comppoprtamiento de funciones, ene ste caso solo queria asegurarme que solo el creador del contrato pudiera mover el Goal modifier onlyOwner{require(msg.sender== owner,"You need to be thhe owner from this contract to change the goal"); _;}//Creamos el evento el cual va a necesitar quien lo hizo y en este caso preferia que cambie la meta de fndeo event changeGoal( address editor, uint256 Goal);//Aqui ponemos la meta a recaudar,solamente el que iniciaiza el contrato puede cambiar este valorfunctionsetGoal(uint256 goal)public onlyOwner {Goal= goal; emit changeGoal(msg.sender, goal);}functionviewGoal()public view returns(uint256){returnGoal;}//Creamos el evento para notificar a los demas que el autor decidio cerrar el fondeo temporalmente event changeState( address editor, bool change);functionchangeProjectState(bool change)public onlyOwner{require(isFundable != change,"Can not change the state with the actual state"); isFundable = change; emit changeState(msg.sender, change);}//Aqui inicia la funcion para fondear el proyectofunctionfundproject()public payable {//Primero evaluamos si el owner del contrato mantiene abiertas las donaciones (tal vez necesita reevaluar algo)require(isFundable,"Owner has decided to stop this fundraising for a while. Stay tuned");//Comprobamos que el total que se ha fondeado sea menor a la metarequire(totalFunded <Goal,"Goal already achieved so you are not able to fund this anymore");//Despues nos aeguramos que la persona mande un minimo,en este caso arriba de 0require(msg.value!=uint(0),"Please add some funds to contribuite to Platzi project");//Comprobamos que el valor que quiere fondear no exceda con a meta que tenemosrequire(totalFunded + msg.value<=Goal,"unable to add more funds, check amount remaining for our goal");//Actualizamos el total que se ha fondeado al contrato totalFunded += msg.value;}//Esta funcion nos sirve para que lla persona pueda ver cuanto se necesita para alcanzar la meta, asi no tendra que estar adivinando cuanto depositar maximofunctionviewRemaining()public view returns(uint256){ uint256 remainingFunds =Goal- totalFunded;return remainingFunds;}}
Para qué sirve "uint(0)" ?
“uint(0)” se refiere a un número entero sin signo de 256 bits que tiene un valor de cero. Esta expresión se utiliza a menudo como un valor por defecto o de referencia en los contratos de Solidity cuando se necesita un valor inicial para una variable de tipo uint. También se utiliza para verificar si una variable uint tiene un valor de cero, ya que la expresión msg.value != uint(0) es verdadera si y sólo si msg.value no es cero.
En resumen, uint(0) se utiliza como una forma de inicializar o verificar valores de tipo uint.
JESUS ! en lo que voy de cursos de blockchain me doy cuenta que el gas en ethereum es un incordio de proporciones mayores O.O ! ahora ya se de donde salieron tantísimas otras "ethereum killers"
Jajaj la plena xD
No, solo es aprender a manejar complejidad que en sí, es un tema avanzado para los programadores.
Si yo soy una persona que quiere hacer una donación haciendo una llamada al contrato, se me cobraria gas al hacer la llamada al contrato?
Si, la persona que quiere hacer la donación tendría que tener esto en cuenta, pues cada llamada a la blockchain va a tener un costo en gas
Felipe es un muy buen profesor y ha logrado transmitir muy bien su conocimiento. Las clases están siendo muy productivas.
Cumpliendo el reto
Validación de aporte "0" y tratar de dar aporte con estado "Closed"
error InvalidFundOrState( uint fundValue, string stateProject
);functionfundProject()public payable notFundFronAuthor{if( msg.value>0&&keccak256(abi.encodePacked(string(state)))==keccak256(abi.encodePacked('Opened'))){ author.transfer(msg.value);// to passe value fund to the author project funds += msg.value; emit SendFunding(msg.sender, msg.value);if(funds >= fundraisingGoal){ emit RaisingFundGoal( id, funds,"fundRaisingGoal reached");}}else{ revert InvalidFundOrState(msg.value, state);}}
Validación de cambio de estado solo permitido a "Opened" y "Closed"
functionchangeProjectState(string calldata newState)public onlyAuthorOrRaisingGoal {require(keccak256(abi.encodePacked(string(newState)))==keccak256(abi.encodePacked("Opened"))||keccak256(abi.encodePacked(string(newState)))==keccak256(abi.encodePacked("Closed")),"Invalid new state"); state = newState; emit ChangeState(id, newState);}```
Hay alguna clase que hable de la optimizacion del gas, buenas practicas en cuanto a eso?
Hola! Tal cual muestra el video, me posicione en la primera address y cree el contrato. Luego, me posicione en la segunda address para aportar 1gw. Funciono perfecto! Lo que no me funciono es al reves, es decir: Me posiciono en la segunda address para crear un nuevo proyecto y lo hizo bien. Pero cuando me posiciono en la primer address y quiero aportar al segundo proyecto, me sale el error de que no puedo porque soy el autor del mismo, cuando el autor era la segunda address. ¿Por favor, me podrías ayudar? Gracias!!
Hola Javier asegúrate de estar interactuando con el contrato que es, si quieres volver a desplegar el contrato desde otra wallet, elimina el que ya esta desplegado para evitar confusiones
Es una buena práctica mantener los errores fuera de la función, o está bien dejar cosas como "require" dentro? (asumiendo que no voy a reutilizarlos)
El require generalmente se usa para hacer validaciones al inicio de una función, si dentro de la ejecución de las funciones hay un error recurrente puedes definirlo fuera de la función y utilizarlo donde se requiera
Ok, gracias!!
Cade vez que se hace una llamada al contrato, se cobra gas?
Así es Fredy
RETO 4:
Observación: Para comparar strings encontré que se genera un hash con: keccak256(abi.encodePacked(str))
// SPDX-License-Identifier: GPL-3.0pragma solidity >=0.7.0<0.9.0;contract projectFundPlatzi{ string public name; bool public isFundable; uint public deposits; uint public depositsGoal; uint private depositTest; address private autor; string public state ="Open"; string private tokenLog ="Log: project status changed"; string private tokenFeedback ="Feedback: Gracias";constructor(string memory _name){ name = _name; isFundable =true; deposits =0; depositsGoal=1000; autor = msg.sender;} error stateNotDefined(string newState); event newFundFeedback( address autor, uint256 deposit, string Feedback); event newFundLog( uint256 depositsGoal, uint256 deposits, string Log); modifier notOnlyOwnerPay{require(autor != msg.sender,"El propietario no puede abonar fondos al proyecto");//la funcion es insertada donde aparece este simbolo _ _;} modifier ownerRequired {require(msg.sender== autor,"Esta accion es restringida al propietario."); _;}functionviewDeposits()public view returns(uint){return deposits;}functionviewRemaining()public view returns(uint256){return depositsGoal - deposits;}functionfundProject(address payable benefactor)public payable notOnlyOwnerPay {require(msg.value>0,"No se acepta abonos sin valor."); depositTest = deposits + msg.value;if(isFundable ==true&& depositTest <= depositsGoal){ benefactor.transfer(msg.value); deposits += msg.value; emit newFundFeedback(autor, msg.value,tokenFeedback);}changeProjectState("Closed");}functionchangeProjectState(string memory _newState)public{require(keccak256(abi.encodePacked(_newState))!=keccak256(abi.encodePacked(state)),"El nombre de estado es igual al actual y no es actualizable");if(keccak256(abi.encodePacked(_newState))==keccak256(abi.encodePacked("Open"))){ isFundable =true; state ="Open";}elseif(keccak256(abi.encodePacked(_newState))==keccak256(abi.encodePacked("Closed"))){ isFundable =false; state ="Closed";}else{ revert stateNotDefined(_newState);} isFundable =!isFundable; emit newFundLog(depositsGoal, deposits, tokenLog);}}
Este es mi avance con los errores programados con el reto #4:
// SPDX-License-Identifier: GPL-3.0pragma solidity >=0.7.0<0.9.0;contract Proyecto_Platzi{ string public projectName; string public projectOwner; uint public projectState =0; address payable ownerWallet; uint public goal; uint public currentFund; error SameStateChange(uint state); error StateClosed(string state); error FundZero(uint fundGiven);constructor(string memory _projectName, string memory _projectOwner, uint _goal){ projectName = _projectName; projectOwner = _projectOwner; ownerWallet =payable(msg.sender); goal = _goal;} modifier onlyOwner(){require( msg.sender== ownerWallet,"Only owner can change the project state");//la función es insertada en donde aparece este simbolo _;} modifier differentToTheOwner(){require( msg.sender!= ownerWallet,"The owner can't fund the project");//la función es insertada en donde aparece este simbolo _;} event ChangeState( uint previousState, uint newState
); event FundValueGiven( uint fundGiven, uint valueToGoal, string greetingMessage
);functionfundProject()public payable differentToTheOwner{if(projectState ==0){if(msg.value!=0){ ownerWallet.transfer(msg.value); currentFund += msg.value; emit FundValueGiven(msg.value,(goal - msg.value),"Thanks for your contribution");}elseif(msg.value==0){ revert FundZero(msg.value);}}elseif(projectState ==1){ revert StateClosed("Sorry, this project is closed");}}functionchangeProjectState(uint newState)public onlyOwner{if(msg.sender== ownerWallet){if(newState != projectState){ emit ChangeState(projectState, newState); projectState = newState;}else{ revert SameStateChange(newState);}}}}
Reto #4
// SPDX-License-Identifier: GPL-3.0pragma solidity >=0.8.0;contract CrowdFunding{ string id; string projectName; uint targetAmount; uint amountFunded; bool isFundable; address payable authorAddress;// Events event projectFunded( address sender, uint amount
); event projectStateChanged( string projectName, string message
);// ./Eventsconstructor(string memory _id, string memory _projectName, uint _targetAmount){ id = _id; projectName = _projectName; targetAmount = _targetAmount; authorAddress =payable(msg.sender); amountFunded =0; isFundable =true;}functionfundProject()public payable isNotAuthor canFund {require(msg.value>0,'The funded amount can not be 0'); authorAddress.transfer(msg.value); amountFunded += msg.value; emit projectFunded(msg.sender, msg.value);}functionchangeProjectState(string calldata newState)public isAuthor { string memory currentState = isFundable ?string('opened'):string('closed');require(keccak256(abi.encode(newState))==keccak256(abi.encode('opened'))||keccak256(abi.encode(newState))==keccak256(abi.encode('closed')),'This state is not defined');require(keccak256(abi.encode(newState))!=keccak256(abi.encode(currentState)),string(abi.encodePacked('This project is already ', currentState )));if(keccak256(abi.encode(newState))==keccak256(abi.encode('opened'))){ isFundable =true;}elseif(keccak256(abi.encode(newState))==keccak256(abi.encode('closed'))){ isFundable =false;} emit projectStateChanged(projectName, isFundable ?'Project opened':'Project closed');}// Function modifiers modifier isAuthor(){require(authorAddress == msg.sender,"You must be the project author!"); _;} modifier isNotAuthor(){require(authorAddress != msg.sender,"As author you can not fund your own project!"); _;} modifier canFund(){require(isFundable ==true,"This project is not available for funding!"); _;}// ./Function modifiersfunctiongetGoal()public view returns(uint){return targetAmount;}functiongetFunds()public view returns(uint){return amountFunded;}functiongetStatus()public view returns(string memory){return isFundable ?'Opened':'Closed';}}
cuando nos muestra un error emite un JSON con el field StateNotDefined
No estaría viendo dónde está mi error porque creo los eventos y los aplico en las funciones correspondientes, pero no veo los logs lanzados a la hora de compilar y ejecutarlas.
Revisa en la info de la transacción
Una cosa no me quedó clara. ¿Cuándo es mejor usar error y cuando es mejor usar require?
Cuando se hace revert la blockchain vuelve al estado original antes de correrse, como una especie del rollBack ?
Hola 👋🏼
Sí, con revert() se eliminan todos las cambios que haya generado el smart contract hasta ese momento.