No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Manejo de errores en Solidity

14/21
Recursos

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: MIT
pragma 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.

image.png

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).

Aportes 31

Preguntas 10

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Errores

  • Dan información específica sobre el fallo
  • 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)

Espero les sirva los comentarios que dejo, se los dejo con amor ❤️

// 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 contrato
     constructor(){
         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 valor
     function setGoal(uint256 goal) public onlyOwner {
         Goal = goal;
         emit changeGoal(msg.sender, goal);
     }
     
     function viewGoal() public view returns(uint256) {
         return Goal;
     }
     //Creamos el evento para notificar a los demas que el autor decidio cerrar el fondeo  temporalmente
     event changeState(
         address editor,
         bool change);
         
     function changeProjectState(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 proyecto
     function fundproject() 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 meta
         require(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 0
         require(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 tenemos
         require(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 maximo
     function viewRemaining() public view returns(uint256){
         uint256 remainingFunds = Goal - totalFunded;
         return remainingFunds;
     }
     
     
 }

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”

Felipe es un muy buen profesor y ha logrado transmitir muy bien su conocimiento. Las clases están siendo muy productivas.

RETO 4:
Observación: Para comparar strings encontré que se genera un hash con: keccak256(abi.encodePacked(str))

// SPDX-License-Identifier: GPL-3.0
pragma 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.");
        _;
    }
    function viewDeposits() public view returns (uint){
        return deposits;
    }

    function viewRemaining() public view returns(uint256){
        return depositsGoal - deposits;
    }

    function fundProject(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");
    }

    function changeProjectState(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";
        }else if (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.0
pragma 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
    );

    function fundProject() 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");
            }
            else if(msg.value == 0){
                revert FundZero(msg.value);
            }

        }
        else if(projectState == 1){
            revert StateClosed("Sorry, this project is closed");
        }
    }

    function changeProjectState(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.0

pragma 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
    );
    // ./Events
    
    constructor(string memory _id, string memory _projectName, uint _targetAmount){
        id = _id;
        projectName = _projectName;
        targetAmount = _targetAmount;
        authorAddress = payable(msg.sender);
        amountFunded = 0;
        isFundable = true;
    }
    
    function fundProject() 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);
    }
    
    function changeProjectState(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;
        } else if(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 modifiers
    
    function getGoal() public view returns(uint){
        return targetAmount;
    }
    
    function getFunds() public view returns(uint){
        return amountFunded;
    }
    
    function getStatus() public view returns(string memory){
        return isFundable ? 'Opened': 'Closed';
    }
}

cuando nos muestra un error emite un JSON con el field StateNotDefined

para lanzar el error usamos revert

Aplicando errores en el reto #4

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

contract CrowdFunding {
    string projectName;
    string projectDescription;
    uint256 goal;
    uint projectStatus;
    uint currentFunds;
    address payable ownerWallet;
    address owner;
    
    error statusIsCosed(uint projectStatus);
    error errorZeroFunds(uint amount);
    
    constructor(string memory _projectName, string memory _projectDescription, uint256 _goal) {
        projectName = _projectName;
        projectDescription = _projectDescription;
        goal = _goal;
        currentFunds = 0;
        
        // Owner Information
        ownerWallet = payable(msg.sender);
        owner = msg.sender;
        
        // Project Status Initialization
        // Status 0 = "Opened"
        // Status 1 = "Closed"
        projectStatus = 0;
    }

    function fundProject() public payable notOwner onlyActiveProjects {
        if (msg.value == 0) {
            revert errorZeroFunds(msg.value);
        }

        ownerWallet.transfer(msg.value);
        currentFunds += msg.value;
        emit NewFundNotification(msg.sender, msg.value);
    }

    function changeProjectStatus(uint newStatus) public onlyOwner {
        require(projectStatus != newStatus, "Project has that state already, choose another!");
        projectStatus = newStatus;
        emit NewStatusChange(newStatus);
    }

    event NewFundNotification(
        address sender,
        uint fundAmount
    );
    
    event NewStatusChange(
        uint newStatus
    );

    modifier onlyActiveProjects() {
        if (projectStatus == 1) { 
            revert statusIsCosed(projectStatus);
        }
        _;
    }

    modifier notOwner() {
        require(owner != msg.sender, "Owners shouldnt send funds to its own projects!");
        _;
    }

    modifier onlyOwner() {
         require(owner == msg.sender, "You must be the project owner!");
         _;
    }

    function getGoal() public view returns(uint) {
        return goal;
    }
    
    function getStatus() public view returns(uint) {
        return projectStatus;
    }
    
    function getFunds() public view returns (uint) {
        return currentFunds;
    }
    
}

Reto clase anterior

pragma solidity ^0.7.0;


contract KickstarterPoor {
        
    string name;
    string description;
    string state = "opened";
    uint256 goal;
    uint256 funds;
     address private owner;
    
    address payable creatorPayable;
    
    
    constructor(string memory _name, string memory _description, uint256 _goal) payable{
        
        name = _name;
        description = _description;
        owner = msg.sender;
        creatorPayable = payable(owner);
        goal = _goal;
        funds = 0;
        
    }
    
   
    
    event newFunds(
        address sender,
        uint256 funds
        );
        
    event projectState(
        uint256 goal,
        uint256 funds,
        string state
        );
    
    
    function checkStatus () public view returns (string  memory){
        
    return state;
    }
    
    function getFunds() public view returns (uint256){
        return funds;
    }
        
    
    
    modifier noFundOwner (){
          require(msg.sender != owner, "You can't donate to yourself, I'll call the police.");
        _;
    }
    
   modifier onlyOwner (){
        require(msg.sender == owner, "You are not admin, go away");
        _;
    }
    
     function FundProyect() public payable noFundOwner{
         creatorPayable.transfer(msg.value);
        funds += msg.value;
        emit newFunds(msg.sender,msg.value);
    }
    
    
    function changeProjectState(string calldata newState) public  onlyOwner{
        
        state = newState;
        
        emit projectState(goal,funds,state);
    }
    
    
}

Cumpliendo el reto

Validación de aporte “0” y tratar de dar aporte con estado “Closed”

  error InvalidFundOrState(
      uint fundValue,
      string stateProject
  );

  function fundProject() 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”

    function changeProjectState(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);
    }```

Omito las variables para el constructor

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract Project {

    address private owner;
    string public state = "open";

    constructor() {
        owner = msg.sender;
    }

    modifier difOwner() {
        require(
            msg.sender != owner,
            "Owner can't fund the project"
        );
        // La función es insertada en donde aparece este símbolo
        _;
    }

    modifier onlyOwnerState() {
        require(
            msg.sender == owner,
            "Only owner can change the project name"
        );
        // La función es insertada en donde aparece este símbolo
        _;
    }

    error projectClosed(string message, string _state);
    event addFunds(string message, address donator, uint amount );

    function fundProject(address payable _donator) public payable difOwner  {
        require(
            msg.value > 0,
            "Debe ingresar un monto mayor a 0" 
        );
    
        if(keccak256(abi.encodePacked(state)) == keccak256(abi.encodePacked("closed"))) {
            string memory msgError = "El proyecto esta cerrado";
            revert projectClosed(msgError, state);
        }else {
            _donator.transfer(msg.value);
            uint amount = msg.value;
            emit addFunds("Gracias por su aporte", _donator, amount);
        }   
    }

    event changeState(string message, string oldState, string newState);

    function changeProjectState(string memory _newState) public onlyOwnerState {
        string memory oldState = state;
        state = _newState;

        emit changeState("El autor ha cambiado el estado", oldState, state);
    }

}

Para comparar 2 string pueden crear una función como esta y usarla para el reto 4.1 y 4.2

    function compareStrings(
        string memory a,
        string memory b
    ) public pure returns (bool) {
        return keccak256(bytes(a)) == keccak256(bytes(b));
    }

Errores en Solidity

  • Estos son de suma importancia, para evitar que un contrato quede bloqueado o que se pierdan los fondos.
  • Para la definicion de errores que podrian suceder en un contrato, hacemos el uso de la palabra reservada Revert, al hacer uso de esta el contrato se detiene la ejecucion e inmediatamente revierte cualquier cambio cualquier cambio que se hubiese realizado antes de lanzar el error.
  • A la hora de lanzar un error, podemos utilizar la funcion Require, esta toma una condicion, si dicha condicion no se cumple, lanza un error.
  • Aparte de require y revert solidity cuenta con otras funciones para manejar errores y excepciones, estas son llamadas assert y throw, estas las utilizamos para probar condiciones de fallos en el contrato. Las Exepcione en solidity tambien tienen la estructura a traves de la palabra clave throw y se capturan con el try/catch.
  • Analogamente
    -Los errores en solidity, podemos verlo como el VAR en un partido de futbol, el contrato seria el juego, supongamos que el equipo A, le anoto un gol al equipo B, antes de la anotacion oficial el juez central recibe a traves del audio que hay posicion adelantada en el gol, el va a revisar y confirma que si habia offside, es decir que no se incrementa el marcador.
    Entonces podria decir,
    El contrato es el juego
    la funcion era la del equipo a anotar el gol al equipo b, en este momento.
    El ERROR seria el offside
    LA FUNCION QUE EJECUTO EL ERROR el VAR (REVERT).
    EL REQUIRE es el reglamento, el cual indica que el ultimo en anotar el gol, no puede estar por delante del ultimo hombre.
    LAS EXCEPCIONES Throw es la potestad que tiene el arbitro de decidir si se cumplen o no las reglas.
    imaginemos la cantidad de dinero que se pierde si no estuviera alguien que hiciera cumplir las reglas en el partido.
    Espero que pueda ser entendible, les agradecería el feedback

Me senti identificado cuando vi en su codigo el require y la condicion logica con el OR jaja, tambien lo hice en clases anteriores sin ver el resutlado que esperaba…

Les comparto mi solución del Reto #4

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;

contract Permission {

    address private owner;
    string public projectName="Platzi";
    uint public state=0;
    address payable public author;
    uint public funds;

    constructor() {
        owner = msg.sender;
        author = payable(msg.sender);
    }

    modifier onlyOwner() {
        require(msg.sender == owner, "No eres el owner");
        _;
    }

    modifier projectFunders() {
        require(msg.sender != owner , "No puedes colaborar a tu propio proyecto");
        _;
    }

    event FundProject(address editor);

    event ChangeProjectState(uint newState);

    //Validación para que no se pueda actualizar un estado, este debe ser diferente al actual.
    error StateError(uint unit, string message);

    //Validación para que no se pueda aportar al proyecto si el estado es "Closed(1)" o se aporta 0ETH.
    error ValueOrStateNotValid(uint unit, uint state);

    function fundProject() public payable projectFunders {
        if(msg.value!=0 && state==0){
            funds += msg.value;
            emit FundProject(msg.sender);
        }
        else{
            revert ValueOrStateNotValid(msg.value, state);
        }
    }
    
    function changeProjectState(uint newState) public onlyOwner {
        if(newState!=state){
            state=newState;
        }
        else{
            revert StateError(newState, "You cannot rewrite the same state");
        }
    }

}

Para manejar errores con mas de un paramentro, es necesario ejecutar el revert con los nombres de esos paramentros entre llaves 🪄

error TestingError(string key1, uint256 key2);
revert({
	key1: "error1",
	key2: 1234
})

Tambien se puede evaluar con assert 🤔
Les dejo una referencia https://solidity-by-example.org/error/

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;
contract reto1{
string public nameProject;
string public projectState=“open”;
bool public isFoundable=true;
uint public valorState=0;
address private owner;

 constructor(
     string memory _nameProject
 ){
     nameProject = _nameProject;
     owner=msg.sender;
 }
 modifier notOwnerfund(){
     require(
         msg.sender!=owner,"No puede depositar el owner"
     );
     _;
 }
 event fundProjectData(address sender,uint256 quantity);
 function fundProject(address payable direction)public payable notOwnerfund{
     require(isFoundable==true, "Project is closed");
     require(msg.value>0,"It cannot be zero contribution");
    direction.transfer(msg.value);
    emit fundProjectData(msg.sender,msg.value);
}
modifier onlyOwnerState(){
    require(
        msg.sender==owner,"No eres el owner"
    );
    _;
}
event changeProjectStateData(address sender, uint newState);
function changeProjectState(uint newState) public onlyOwnerState{
    if(newState==0){
        require(newState!=valorState, "It is the same state");
        projectState="open";
        isFoundable=true;
        valorState=0;
    }else if(newState==1){
        require(newState!=valorState, "It is the same state");
        projectState="closed";
        isFoundable=false;
        valorState=1;
    }
     emit changeProjectStateData(msg.sender,newState);
}

}

RESUMEN CLASE 14:
ERRORES

  • Dan información sobre fallos.

  • Revierte los cambios aplicados.

RETO 4:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract CrowdFunding {
    string public id;
    string public name;
    string public description;
    address payable public author;
    uint256 public state;
    uint256 public funds;
    uint256 public fundraisingGoal;

    event ProjectFunded(string projectId, uint256 value);

    event ProjectStateChanged(string id, uint256 state);

    constructor(
        string memory _id,
        string memory _name,
        string memory _description,
        uint256 _fundraisingGoal
    ) {
        id = _id;
        name = _name;
        description = _description;
        fundraisingGoal = _fundraisingGoal;
        author = payable(msg.sender);
    }

    modifier isAuthor() {
        require(author == msg.sender, "You need to be the project author");
        _;
    }

    modifier isNotAuthor() {
        require(
            author != msg.sender,
            "As author you can not fund your own project"
        );
        _;
    }

    function fundProject() public payable isNotAuthor {
        require(state != 1, "The project can not receive funds");
        require(msg.value > 0, "Fund value must be greater than 0");
        author.transfer(msg.value);
        funds += msg.value;
        emit ProjectFunded(id, msg.value);
    }

    function changeProjectState(uint256 newState) public isAuthor {
        require(state != newState, "New state must be different");
        state = newState;
        emit ProjectStateChanged(id, newState);
    }
}

Si usamos un variable de tipo string como se estaba utilizando hasta este momento(“Open” || “Closed”), esto abre la oportunidad a errores de tipeo de parte del user al cambiar el estado.
Para que esto se pueda manejar de una manera mas simple, el estado del proyecto podria ser una variable de tipo Bool, Ejemplo:

bool IsActive;

El mismo puede tomar valores true o false. Y para realizar el cambio de estado de un proyecto, lo podriamos hacer de la siguiente manera:

function changeStatus() public onlyOwner {
        IsActive = !IsActive;
        emit changeStatusEvent(IsActive);
    }

Funcion de los errores

**Esto es en parte aportes, código del curso y una pequeña parte de código propio espero que les sirva **

<// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract CrowFounding {

    string public id;
    string public name;
    string public description;
    address payable public author;
    string public state = "Opened";
    uint public funds;
    uint public fundraisingGoal;


    // constructor (solo se ejecutal al momento del despliegue)
    constructor(
        string memory _id,
        string memory _name,
        string memory _description,
        uint _fundraisingGoal) {
            id = _id;
            name = _name;
            description = _description;
            fundraisingGoal = _fundraisingGoal;
            author = payable(msg.sender);
    }

    // controlo q solo el dueño pueda cambiar el estado
    modifier onlyOwner() {
        require(msg.sender == author, "Only owner can change the project state.");
        //la función es insertada en donde aparece este símbolo
        _;
    }

    // controlo q el dueño no pueda aportar
    modifier notOwner() {
        require(msg.sender != author, "Owner cannot add to the proyect.");
        //la función es insertada en donde aparece este símbolo
        _;
    }

    event depositedAmount(
        address contributor,
        string projectId,
        uint amount
    );

    event projectStateChange(
        string id,
        string state
    );

    // funcion para aportar
    function fundProject() public payable notOwner {
        author.transfer(msg.value);
        funds += msg.value;
        emit depositedAmount(msg.sender, id , msg.value);
    }

    // funcion para cambiar el estado del proyecto
    function changeProjectState(string calldata newState) public onlyOwner {
        state = newState;
        emit projectStateChange( id, newState );
    }

}

> 


Para el reto ya tenia el contrato de fondeo con varios require de esta manera:

//IF the project is still alive
        require(Isfondeable, "The project is not longer available");

        //If the goal is not reach yet
        require(totalFunded < Goal, "The Goal is already reach");

        //Check valid amount
        require(msg.value != uint(0), "Please add a amount to contribute to the project");

        //Check not exceed the goal
        require(totalFunded + msg.value <= Goal, "unable to add more funds, check amount remaining for our goal");

Pero por tema del reto, agregue una validación con un if y error en el cambio de estado:

function ChangeProjectState(bool change) public OnlyOwner {
        if(Isfondeable == change) {
            revert IsSameState(change, "Can't change the state to same status");
        }
        Isfondeable = change;
        emit ProjectStateEvent(change, id);
    }

aporto mi ejemplo al reto 4

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract CrowdFunding {
// variables (q uso en el contructor)
string public id;
string public name;
string public description;
address payable public author;

//variables ....q no uso en el costructor
//string public state = "Opened"; 0 open 1 close
uint public state = 0; // 0 open 1 close
uint256 public funds;
uint256 public fundraisingGoal;

// evento para saber quien aporto y cuanto queda
event ChangeFound(
    address editor,
    uint256 fundRestante

);

// constructor (solo se ejecutal al momento del despliegue)
constructor(
    string memory _id,
    string memory _name,
    string memory _description,
    uint256 _fundraisingGoal
) {
    id = _id;
    name = _name;
    description = _description;
    fundraisingGoal = _fundraisingGoal;
    author = payable(msg.sender);
}

// controlo q solo el dueño pueda cambiar el estado
modifier onlyOwner() {
    require(msg.sender == author, "Only owner can change the project state.");
    //la función es insertada en donde aparece este símbolo
    _;
}

// controlo q el dueño no pueda aportar
modifier notOwner() {
    require(msg.sender != author, "Owner cannot add to the proyect.");
    //la función es insertada en donde aparece este símbolo
    _;
}

// funcion para aportar
function fundProject() public payable notOwner {
    require(state == 0, "Proyect is closed");
    require(msg.value > 0, "Not mouse acepted.");
    author.transfer(msg.value);
    funds += msg.value;
    emit ChangeFound(msg.sender, fundraisingGoal - funds);
}


// funcion para cambiar el estado del proyecto
//function changeProjectState(string calldata newState) public onlyOwner {
    function changeProjectState(uint newState) public onlyOwner {
    require(newState == 0 || newState == 1, "Value not defined");
    state = newState;
}

}

Me encantan estas clases, aunque al principio era un poco confuso la sintaxis de sollidity, ya me voy familiarizando de a poco

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract CrowdFunding2 {
    string public id;
    string public name;
    string public description;
    address payable public author;
    uint256 public state = 0;
    uint256 public funds;
    uint256 public fundraisingGoal;

    constructor(
        string memory _id,
        string memory _name,
        string memory _description,
        uint256 _fundraisingGoal
    ) {
        id = _id;
        name = _name;
        description = _description;
        fundraisingGoal = _fundraisingGoal;
        author = payable(msg.sender);
    }
    modifier onlyOwner(){
        require(msg.sender == author,"Just the owner can do this action");
         _;
    }
    modifier excludeOwner(){
        require(msg.sender != author,"Owner can not do this action");
         _;
    }
    
    event projectFunded(uint256 lastfunds, uint256 income);
    event stateChanged(address editor, uint256 newState);

    error NoZeroFunds(uint256 unit);

    function fundProject() public payable excludeOwner {
        if(state == 1){
            require(state == 1, "The contract is already closed, funds are not acepted");
        }else if (state == 0){
            if (msg.value == 0){
                revert NoZeroFunds(msg.value);
            }
            author.transfer(msg.value);
            funds += msg.value;
            emit projectFunded(funds-msg.value, msg.value);
        }

    }

    function changeProjectState(uint256 newState) public onlyOwner {
        require(newState != state, "The contract is already in that state");
        state = newState;
        emit stateChanged(msg.sender, state);
    }
}

Mi solución al reto #4 😃

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;

contract crowfundingContract {
    //Definir los datos iniciales de quien despliegue el contrato (i.e. el que empieza su crowfunding con la
    //estructura de este contrato)
    string public id;
    string public projectName;
    string public description;
    string public status = "Opened";
    uint public statusID = 0;
    address payable public author;
    uint public fundingGoal;
    uint public funds;

    event fundedBy (
        string project,
        address participant,
        uint vulue
    );
    event changedBy (
        string project,
        address author,
        string newStatus
    );

    // reto#4
    error noFundSelected (uint funds, string err);
    error statusAlreadySelected (uint status);
    error notAnOption (uint status);
    error projectClosed (string err);
    // reto#4

    //Crear el constructor que inicializa variables relevantes a quién inicie un corwfunding
    constructor(string memory _id, string memory _projectName, string memory _description, uint _fundingGoal){
        author = payable(msg.sender);
        id = _id;
        projectName = _projectName;
        description = _description;
        fundingGoal = _fundingGoal;
    }
    //Crear una función que permita a quién la llame aportar al crowfunding deseado
    function fundProject() public payable fundRestriction {
        if (statusID == 0){
            if (msg.value == 0){
            revert noFundSelected(msg.value, "You cannot fund with 0 wei!");
            }else{
                author.transfer(msg.value);
                funds += msg.value;
                emit fundedBy(id, msg.sender, msg.value);
            }
        }else{
            revert projectClosed ("Project is currently closed, you cannot fund it!");
        }
        
    }

    //Crear la función que permita cambiar el estado del proyecto
    function changeProjectStatus(uint newStatus) public changeStatusRestriction {
        if(newStatus != statusID){
            if (newStatus == 0){
            status = "Opened";
            statusID = newStatus;
            emit changedBy(id, msg.sender, status);
            }else if (newStatus == 1){
            status = "Closed";
            statusID = newStatus;
            emit changedBy(id, msg.sender, status);
            }else{
                revert notAnOption (newStatus);
            }
        }else {
            revert statusAlreadySelected (newStatus);
        }     
    }
    //Crear el modificador para que sólo quién despliega el contraro en la red de ethereum pueda modificar el estado
    //del proyecto a financiar
    modifier changeStatusRestriction(){
        require(
            msg.sender == author,
            "Only the owner can change the status"
        );
        _;
    }
    //Crear la función modifier que evita que el creador del crowfunding aporte a su propio proyecto
    modifier fundRestriction(){
        require(
            msg.sender != author,
            "You as the author cannot fund this project"
        );
        _;
    }

}

Creo que por ahi puede ser…

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.7.0 <0.9.0;

contract CrowdFunding {
    string public id;
    string public name;
    string public description;
    address payable public author;
    // string public projectState = "Open"; //por defecto abierto a recibir aporte
    uint public funds;
    uint256 public fundGoal; //define cuanto espero ganar con la ronda de levantamento de capital
    
    uint public projectStatus;

    constructor(string memory _id, string memory _name, string memory _description, uint256 _fundGoal){
        id = _id;
        name = _name;
        description = _description;
        fundGoal = _fundGoal;
        // Owner Information
        author = payable(msg.sender); //msg.sender por defecto no recibe ETH pero la convertimos a 'payable'
        
        // Status 0 = "Close"
        // Status 1 = "Open"
        projectStatus = 1;
    }

    //registrar fondos del proyecto. Con 'event' compatimos los datos ala Blockchain
    event ProjectFunded(string projectId, uint256 value);

    event ProjectStateChanged(string id, uint state);

    // solo podemos modificar el estado del projecto siendo el propietario
    modifier onlyAuthor() {
        require(author == msg.sender ,
        "Only owner can change state of project"
        );
        _;
    }

    modifier noSelfFunds() {
        require(
             author != msg.sender,
            "Autofinance is not available"
        );
        _;
    }


    //Manjeo de errores
    error ProjectClosed(uint projectStatus);
    error NoZeroFunds(uint amount);

    //caulquier persona la puede ver y se puede enviar ETH sin problemas
    function fundProject() public payable noSelfFunds {

        //Project close
        require(projectStatus != 1, "Project is already close. No more funds are collected");
        
        //validacion para que no se puedan hacer aportes de 0
        if(msg.value == 0){
            revert NoZeroFunds(msg.value);
        }
        author.transfer(msg.value); //transfiere el valor del usuario al autor
        funds += msg.value; //despues lo agrego a los fundos del proyecto para registrar el aporte

        emit ProjectFunded(id, msg.value);
    }

    //modifica el estado del proyecto. Calldata ahorra gas, solo existe cuando se le llama a la funcion
    function changeProjectState(uint newState) public onlyAuthor{
        require(projectStatus != newState, "Project has already that state");
        projectStatus = newState;
        emit ProjectStateChanged(id, newState);
    }

}

Esta muy bueno el curso, como esta manejando la clase y el proyecto al mismo tiempo, asi llevamos la teoria vista aplicada en la practica y poco a poco va tomando mas complejidad el contrato 😄