Establecer tipos de datos personalizados con Struct Types
Resumen
La complejidad de un contrato inteligente puede ser de un nivel tan alto que no nos alcancen los tipos de variables que este lenguaje tiene para ofrecer y debamos declarar nuestros tipos de datos personalizados.
Estructuras de datos
Puedes declarar un nuevo tipo de dato conocido como estructuras que permiten agrupar N cantidad de variables relacionadas entre sí, cada una de un tipo diferente.
Si tienes conocimiento en programación orientada a objetos, sabrás lo que es una clase y cómo instanciar la misma te genera un objeto. También puedes entender el uso de las estructuras similar que las Interfaces en TypeScript. Conjuntos de datos relacionados que suelen representar un registro. Solidity implementa el concepto de estructura inspirado en C/C++.
Utiliza la palabra reservada struct para crear una estructura de datos. Así podrás tener dentro cada propiedad la estructura y su respectivo tipo.
Genera una variable de este tipo, basta con invocar el constructor de la estructura y asignarle el tipo a la variable.
Persona p =Persona("Lionel","Messi",35);
Emplea los datos dentro de una estructura de manera intuitiva, ten en cuenta que es prácticamente igual a la utilización de un objeto.
p.nombre;// Lionelp.apellido;// Messip.edad;// 35
Dentro de una estructura, podrás tener otra estructura, arrays o cualquier otro tipo de dato.
Estos conocimientos te permitirán desarrollar mejores contratos y, sobre todo, más limpios. Cuando necesitamos manipular una gran cantidad de datos, podemos agruparlos de forma lógica en una estructura para garantizar un acceso más organizado.
Contribución creada por:Luis Enrique Herrera y Kevin Fiorentino (Platzi Contributors).
Solidity permite al usuario crear su propio tipo de datos en forma de estructura. La estructura contiene un grupo de elementos con un tipo de datos diferente. Generalmente, se usa para representar un registro. Para definir una estructura se utiliza la palabra clave struct, que crea un nuevo tipo de datos.
Anteriormente la variable state estaba declarada como string e inicializada con un valor, así: string public state = "Opened", en está clase se cambió el tipo de dato a uint, pero sin inicializar el valor de la variable quedando ahora así: uint public state. Al no darle un valor inicial la variable adquiere un valor por defecto, para el tipo de datos uint este valor por defecto es 0. Más información en la Documentación de Solidity
Ahhhh con que eso era, ya me decia como es que sabe la funcion que el estado no es 0 si no estaba definido. Mchas gracias por ese dato
Solución reto 5
// SPDX-License-Identifier: MITpragma solidity ^0.8.0;contract Crowfunding{ struct Project{ int id; string name; bool isActive; address payable owner; uint founds; uint goal;}Projectpublic project;constructor(string memory _name, uint _goal){ project =Project(1, _name,true,payable(msg.sender),0, _goal);} event FundProject(address donor, uint quantity); event ChangedState(bool isActive); modifier onlyOwner(){require(project.owner== msg.sender,"You are not the project owner"); _;} modifier notOwner(){require(project.owner!= msg.sender,"You cannot transfer to your own project"); _;} modifier onlyWhenActive(){require(project.isActive,"This project is not active."); _;}functionfundProject()public payable notOwner onlyWhenActive {require(msg.value>0,"You cannot fund 0 eth"); project.owner.transfer(msg.value); project.founds+= msg.value; emit FundProject(msg.sender, msg.value);}functionchangeProjectState()public onlyOwner { project.isActive=!project.isActive; emit ChangedState(project.isActive);}}
Aqui les dejo la forma de comparar strings en solidity:
keccak256(bytes("open")) == keccak256(bytes("open"))
Batman: Poder "Soy millonario" XD
Los struct me recuerdan a las "class" o "interface" en otros lenguajes.
Me volví loco con la function fundProject() pero igualmente no logro compilar por un error cuando declaro los errores.
Alguien me puede aclarar el por qué?
Así después podré comprobar si el churro que hice después está bien... :D
// SPDX-License-Identifier: GPL-3.0pragma 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; error StateNotDefined(uint256 unit); error ClosedStateError(uint256 value); event ChangeStatusEvent(string id, uint256 state); event FundEvent(string id, uint256 value);constructor(string memory _id, string memory _name, string memory _description, uint256 _fundraisingGoal){ id = _id; name = _name; description = _description; fundraisingGoal = _fundraisingGoal; state =1; author =payable(msg.sender);} modifier isOwner(){require(msg.sender== author,"You are not the owner of this shit"); _;} modifier isNotOwner(){require(msg.sender!= author,"You are the owner of this wallet!"); _;}functiongetProjectState()privatereturns(uint256 projectState){ projectState = state;}functionfundProject()public payable isNotOwner(){require(getProjectState()==1,"The state is CLOSED, try again later!");if(getProjectState()==1){require(msg.value>0,"You have to put some coins in my pocket dude!");if(msg.value>0){ author.transfer(msg.value); funds += msg.value; emit FundEvent(id, msg.value);}else{ revert NoMoneyDetected(msg.value);}}else{ revert ClosedStateError(getProjectState());}}functionchangeProjectState(uint256 newState)public isOwner {require(state != newState,"New state must be different"); state = newState; emit ChangeStatusEvent(id, newState);}}
fíjate que no definiste el error NoMoneyDetected :eyes:
Otra cosa, crear la función getProjectState me parece interesante si tuviéramos que hacer algún calculo más complejo para hallar el estado de un proyecto, pero si te das cuenta en realidad no necesita un calculo complejo pues el estado ya esta definido en una variable de estado.
Gracias por la pronta respuesta! En el código que copié tengo una errata al declarar los errors, en el que tengo aquí si las declaré.
Me sale error como que falta un ' ; '
Sobre la función getProjectState tiene razón, me compliqué bastante! :D
// SPDX-License-Identifier: GPL-3.0pragma 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 ChangeStatusEvent(string id , uint256 state); event FundEvent(string id , uint256 value); error NoMoneyDetected(uint256 value); error ClosedStateError(uint256 value);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 isOwner(){require(msg.sender== author,"You are not the owner of this shit"); _;} modifier isNotOwner(){require(msg.sender!= author,"You are the owner of this wallet!"); _;}functiongetProjectState()privatereturns(uint256 projectState){ projectState = state;}functionfundProject()public payable isNotOwner(){//compruebo si el state esta en 1 (abierto), sino, lanzo errorrequire(getProjectState()==1,"The state is CLOSED, try again later!");if(getProjectState()==1){//solicito que haya alguito en la transferrequire(msg.value>0,"You have to put some coins in my pocket dude!");if(msg.value>0){ author.transfer(msg.value); funds += msg.value; emit FundEvent(id, msg.value);}else{ revert NoMoneyDetected(msg.value);}}else{ revert ClosedStateError(getProjectState());}}functionchangeProjectState(uint256 newState)public isOwner {require(state != newState,"New state must be different"); state = newState; emit ChangeStatusEvent(id, newState);}}```
El struct es como un tipo de clase en otros lenguajes de programación ? puede tener métodos ?
Me sonó igual, una clase como tal, supongo que ahí podría estar la diferencia entre función y método… Ojalá el profe nos saque de dudas.
Recuerden que al convertir la variable “state” de string a uint, deben hacerlo igual en:
Event ProjectStateChanged()
function changeProjectState(uint newState)
Recuerden que Solidity es altamente tipado sino se hacen los cambios el compilador les dara error.
Hola buen día, me gustaría saber si existe alguna diferencia al declarar el error y utilizar revert, en lugar de solo utilizar un require, tiene que ver algo con el gas, lo pregunto pues creería que al utilizar error se necesitaría mas procesamiento que con solo el require
El require por lo general se usa para hacer validaciones al inicio de una función, los requisitos para que una función se ejecute.
El revert se usa para que en caso de que ocurra un error, todo vuelva al estado en el que estaba antes del llamado de la función
LA VERDAD NO ENTIENDO, DONDE PUEDO TENER UNA CLASE MAS BASICA POR FAVOR
Para guardar la información en el struct tenemos que eliminar el constructor o ambos quedan en el contrato?
Hola 👋🏼
Ambos quedan dentro.
El constructor es un segmento de código que se ejecuta solamente cuando se despliegua el contrato. El struct es una estructura de datos para guardar información.
Cual es la diferencia de usar los struct types con la anterior forma con la que habiamos empezado a trabajar? Ya no necesito el constructor?
Vas a seguir necesitando el constructor porque el constructor te permite darle un valor inicial a una variable. Ahora, como puedes ver todas las variables de estado que teníamos definidas en el smart contract eran propiedades de un elemento, el proyecto, lo que nos permite struct es en vez de tener muchas variables, podemos definir una sola que agrupe todas las propiedades de un elemento
Gracias profe
Porque me sale el siguiente mensaje cuando hago desde otra direccion que "aporto" 0 gwei?
The transaction has been reverted to the initial state.
Note: The called function should be payable if you send value and the value you send should be less than your current balance.
Debug the transaction to get more information.
here the code
// SPDX-License-Identifier: GPL-3.0pragma 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// projectStatus 0 = "Close"// projectStatus 1 = "Open" 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'}//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"); _;}//No podemos recibir fondos propios modifier noSelfFunds(){require( author != msg.sender,"Autofinance is not available"); _;}//caulquier persona la puede ver y se puede enviar ETH sin problemasfunctionfundProject()public payable noSelfFunds {//Project closerequire(projectStatus !=1,"Project is already close. No more funds are collected");//validacion para que no se puedan hacer aportes de 0require(msg.value>0,"No zero funds are allowed"); 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 funcionfunctionchangeProjectState(uint newState)public onlyAuthor{require(projectStatus != newState,"Project has already that state"); projectStatus = newState; emit ProjectStateChanged(id, newState);}}
Como has añadido una restricción usando el require no vas a poder hacer aportes de 0, ahora al parecer el error que te muestra no es por esta validación, que environment estas usando en remix ?
Cumpliendo el reto
// SPDX-License-Identifier: SEE LICENSE IN LICENSEpragma solidity >=0.7.0;contract crowdfunding { struct Project{ string id; string name; string description; address payable author;//who create o represent the project uint state; uint funds;// to acumulate project funds uint fundraisingGoal;// the goal of the funding}Projectpublic project;constructor(string memory _id, string memory _name, string memory _description, uint _fundraisingGoal){ project =Project( _id, _name, _description,payable(msg.sender),0,0, _fundraisingGoal
);} modifier notFundFronAuthor {require( msg.sender!= project.author,"Author don't funding his ouw project"); _;} event SendFunding( address funder, uint fund
); event RaisingFundGoal( string idProject, uint fundRaising, string mensaje
);functionfundProject()public payable notFundFronAuthor{require(msg.value>0,"Fund value must be greater then 0");require(project.state!=1,"The project can not receive funds, it's closed"); project.author.transfer(msg.value); project.funds+= msg.value; emit SendFunding(msg.sender, msg.value);if(project.funds>= project.fundraisingGoal){ emit RaisingFundGoal( project.id, project.funds,"fundRaisingGoal reached");}} modifier onlyAuthorOrRaisingGoal {require( msg.sender== project.author|| project.funds>= project.fundraisingGoal,"Only author can change state or if the fundraidingGoal is raising"); _;// identifica desde donde se inserta la función, define cuando se continua la función} event ChangeState( string idProject, uint newState
);functionchangeProjectState(uint newState)public onlyAuthorOrRaisingGoal {require( newState ==1|| newState ==0,"Invalid new state. It'll be 0 to Opened and 1 to Closed"); project.state= newState; emit ChangeState(project.id, newState);}}
Mi solución:
// 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 );functionfundProject(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);functionchangeProjectState(string memory _newState)public onlyOwnerState { string memory oldState = state; state = _newState; emit changeState("El autor ha cambiado el estado", oldState, state);}
}
Excelente explicación !
El uso de las struct types, es para reducir el coste del gas. Ya que con este tipo podemos agrupar elementos. que pueden ser usados en mapping o array