Cryptozombies Notes – 加密僵尸笔记区块链毕设代写

区块链毕设代写本文提供国外最新区块链项目源码下载,包括solidity,eth,fabric等blockchain区块链,Cryptozombies Notes – 加密僵尸笔记区块链毕设代写 是一篇很好的国外资料

Cryptozombies Notes

Outlines core Solidity concepts covered in the Cryptozombies online course. Consists of a mixture of lesson text and original summaries.

Check out the Solidity Cheatsheet for a quick summary of some of these concepts.

Resources

  • Official Solidity Cheatsheet

Table of Contents

  • Lesson 1: Solidity Basics
    • Contracts
    • State Variables And Integers
      • State Variables
      • Unsigned Integers: uint
    • Math Operations
    • Structs
    • Arrays
    • Function Declarations
      • Reference Types
    • Working with Structs And Arrays
    • Private And Public Functions
    • Internal And External Functions
    • More on Functions
      • Return values
      • Functions modifiers
    • Keccak256 And Typecasting
      • keccak256
      • Typecasting
    • Events
    • Web3.js
  • Lesson 2: Beyond Basics
    • Mappings And Addresses
      • Addresses
      • Mappings
    • Msg.sender
    • Require
    • Inheritance
    • Import
    • Storage vs. Memory (Data Location)
    • Interacting With Other Contracts
    • Using an Interface
    • Handling Multiple Return Values
    • If Statements
  • Lesson 3: Advanced Solidity Concepts
    • Immutability of Contracts
    • Ownable Contracts
    • Function Modifiers
    • Gas
      • What’s Gas?
      • Why Is Gas Necessary?
      • How to Save Gas by Packing Structs
    • Time Units
    • Passing Structs as Arguments
    • More on Function Modifiers

Lesson 1: Solidity Basics

Contracts

Solidity’s code is encapsulated in contracts. A contract is the fundamental building block of Ethereum applications — all variables and functions belong to a contract, and this will be the starting point of all your projects.

An empty contract named HelloWorld would look like this:

contract HelloWorld {  }

All solidity source code should start with a “version pragma” — a declaration of the version of the Solidity compiler this code should use. This is to prevent issues with future compiler versions potentially introducing changes that would break your code.

Here’s an example of a smart contract that will be compiled by any compiler with a version 0.7.3 or higher:

pragma solidity >=0.7.3;  contract HelloWorld {  }

State Variables And Integers

State Variables

State variables are permanently stored in contract storage. They’re written to the Ethereum blockchain. Permanently. (Unlike data stored in memory, which we’ll discuss later.)

When we work with smart contracts, we store and access data from the blockchain rather than traditional databases. Therefore, using state variables in our contracts is like the equivalent of storing this information in a database.

contract Example {   // This will be stored permanently in the blockchain   uint myUnsignedInteger = 100; }

In this example contract, we created a uint called myUnsignedInteger and set it equal to 100.

Unsigned Integers: uint

The uint data type is an unsigned integer, meaning its value must be non-negative. For signed integers, you can use the int type.

uint is actually equivalent to uint256, a 256-bit unsigned integer. You can also use uints with fewer bits (uint8, uint16, uint32…) to be more cost-efficient, which we’ll also discuss later.

Math Operations

Solidity supports the same math operations you’d expect from a programming language:

  • Addition: x + y
  • Subtraction: x – y,
  • Multiplication: x * y
  • Division: x / y
  • Modulus / remainder: x % y
  • Exponents: x ** 2

Structs

A struct is a complex data type that has multiple properties:

struct Person {   uint age;   string name; }

Arrays

Arrays in Solidity are similar to arrays in JavaScript.

Solidity has two types of arrays:

  • Fixed arrays: Have fixed lengths.
  • Dynamic arrays: Have no fixed size and can contain any number of elements.
// Array with a fixed length of 2 elements: uint[2] fixedArray; // another fixed Array, can contain 5 strings: string[5] stringArray; // a dynamic Array - has no fixed size, can keep growing: uint[] dynamicArray;

You can also create an array of structs. Remember the Person struct from the last lesson?

Person[] people; // dynamic Array, we can keep adding to it

Since state variables are stored permanently in the blockchain, creating a dynamic array of structs like this can be useful for storing structured data in your contract — like a database, remember?

Public Arrays

You can declare an array as public , and Solidity will automatically create a getter method for it. Here’s what the syntax looks like:

Person[] public people;

Other contracts could then read from this array, but not write to it. This makes it a useful pattern for storing public data in your contracts.

Function Declarations

Function declarations in Solidity look like this:

function eatHamburgers(string memory _name, uint _amount) public {  }

The above function, eatHamburgers, takes two parameters:

  • A string called _name
  • A uint called _amount

Note that the function is public, meaning that it can be called internally or via messages (externally).

Why do both parameters start with an underscore? It’s convention to start a function parameter variable name with an underscore (e.g. _name instead of name) to differentiate them from global variables.

Reference Types

In Solidity, a string is a reference type, while a uint is a value type.

There are two ways to pass arguments to Solidity functions:

  • By value – the solidity creates a new copy of the param’s value and passes it to the function. The value of the original parameter is not changed.
  • By reference – The functions is called with a reference to the original value. If the function changes the value of the variable received, the original variable is also mutated.

Working with Structs And Arrays

Let’s look back at the Person struct we created earlier:

struct Person {   uint age;   string name; }  Person[] public people;

Now let’s create new Persons and add them to the people array:

// create a New Person: Person satoshi = Person(172, "Satoshi");  // Add that person to the Array: people.push(satoshi);

Let’s do this all in one line:

people.push(Person(172, "Satoshi"));

Note that array.push() adds items to the end of an array.

uint[] numbers; numbers.push(5); numbers.push(10); numbers.push(15); // numbers is now equal to [5, 10, 15]

Private And Public Functions

In Solidity, functions are public by default. This means that anyone (or any other contract) can call your contract’s function and execute its code.

This can create security concerns, as it potentially leaves your contract vulnerable to attacks. It’s a good practice to make your functions private by default (meaning that they can only be accessed from within the same contract) and then only mark a function public if you want to expose it to everyone.

Here’s an example of how to declare a private function:

uint[] numbers;  function _addToArray(uint _number) private {   numbers.push(_number); }

Only functions within this same contract will be able to call _addToArray and add to the numbers array.

Note the _ at the beginning of `_addToArray. Just like with function parameters, it’s customary to start private functions with an underscore.

Internal And External Functions

Solidity also provides us with two more types of visibility for functions: internal and external.

internal is the same as private, but with one bonus: it’s also accessible to contracts that inherit from this contract.

external is similar to public, but external functions can only be called outside the contract. Unlike public, they can’t be called by other functions inside that contract.

More on Functions

Return values

If a function returns a value, you should also specify the type when declaring the function:

string greeting = "What's up dog";  function sayHello() public returns (string memory) {   return greeting; }

Function modifiers

The above function (sayHello) doesn’t modify state — it just returns a pre-existing state variable. Therefore, it can be declared a view function:

string greeting = "What's up dog";  function sayHello() public view returns (string memory) {   return greeting; }

You can also write pure functions, which don’t access any data in the app.

function _multiply(uint a, uint b) private pure returns (uint) {   return a * b; }

Keccak256 And Typecasting

keccak256

keccak256 is a hash function built into Ethereum and is a version of SHA3. It has many purposes in Ethereum, but we’ll use it to randomly generate a number.

Note hat keccak256 expects a single parameter of type bytes. This requires us to “pack” our parameters before calling keccak256:

//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5 keccak256(abi.encodePacked("aaaab")); //b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9 keccak256(abi.encodePacked("aaaac"));

Typecasting

Solidity lets you convert between data types. For example:

uint8 a = 5; uint b = 6; // throws an error because a * b returns a uint, not uint8: uint8 c = a * b; // we have to typecast b as a uint8 to make it work: uint8 c = a * uint8(b);

Events

Events let your contract communicate with your app’s frontend, which can listen for certain events and perform actions when they happen.

// declare the event event IntegersAdded(uint x, uint y, uint result);  function add(uint _x, uint _y) public returns (uint) {   uint result = _x + _y;   // fire an event to let the app know the function was called:   emit IntegersAdded(_x, _y, result);   return result; }

A JavaScript implementation for this event on the frontend could look something like this:

YourContract.IntegersAdded(function (error, result) {   // do something with result });

Web3.js

Ethereum has a JavaScript library called Web3.js.

We’ll go into this later, but here’s an example of how a frontend could use the library to interact with a smart contract.

Contract

pragma solidity ^0.4.25;  contract ZombieFactory {      event NewZombie(uint zombieId, string name, uint dna);      uint dnaDigits = 16;     uint dnaModulus = 10 ** dnaDigits;      struct Zombie {         string name;         uint dna;     }      Zombie[] public zombies;      function _createZombie(string _name, uint _dna) private {         uint id = zombies.push(Zombie(_name, _dna)) - 1;         emit NewZombie(id, _name, _dna);     }      function _generateRandomDna(string _str) private view returns (uint) {         uint rand = uint(keccak256(abi.encodePacked(_str)));         return rand % dnaModulus;     }      function createRandomZombie(string _name) public {         uint randDna = _generateRandomDna(_name);         _createZombie(_name, randDna);     }  }

Frontend, using Web3.js

// Here's how we would access our contract: var abi = /* abi generated by the compiler */ var ZombieFactoryContract = web3.eth.contract(abi) var contractAddress = /* our contract address on Ethereum after deploying */ var ZombieFactory = ZombieFactoryContract.at(contractAddress) // `ZombieFactory` has access to our contract's public functions and events  // some sort of event listener to take the text input: $("#ourButton").click(function(e) {   var name = $("#nameInput").val()   // Call our contract's `createRandomZombie` function:   ZombieFactory.createRandomZombie(name) })  // Listen for the `NewZombie` event, and update the UI var event = ZombieFactory.NewZombie(function(error, result) {   if (error) return   generateZombie(result.zombieId, result.name, result.dna) })  // take the Zombie dna, and update our image function generateZombie(id, name, dna) {   let dnaStr = String(dna)   // pad DNA with leading zeroes if it's less than 16 characters   while (dnaStr.length < 16)     dnaStr = "0" + dnaStr    let zombieDetails = {     // first 2 digits make up the head. We have 7 possible heads, so % 7     // to get a number 0 - 6, then add 1 to make it 1 - 7. Then we have 7     // image files named "head1.png" through "head7.png" we load based on     // this number:     headChoice: dnaStr.substring(0, 2) % 7 + 1,     // 2nd 2 digits make up the eyes, 11 variations:     eyeChoice: dnaStr.substring(2, 4) % 11 + 1,     // 6 variations of shirts:     shirtChoice: dnaStr.substring(4, 6) % 6 + 1,     // last 6 digits control color. Updated using CSS filter: hue-rotate     // which has 360 degrees:     skinColorChoice: parseInt(dnaStr.substring(6, 8) / 100 * 360),     eyeColorChoice: parseInt(dnaStr.substring(8, 10) / 100 * 360),     clothesColorChoice: parseInt(dnaStr.substring(10, 12) / 100 * 360),     zombieName: name,     zombieDescription: "A Level 1 CryptoZombie",   }   return zombieDetails }

Lesson 2: Beyond Basics

Mappings And Addresses

Addresses

The Ethereum blockchain is made up of accounts (like bank accounts). Each account has a balance of Ether. You can send and receive Ether payments to other accounts (just like a bank account can wire money to and receive transfers from other accounts).

Each account has an address — similar to an account number. It looks like this:

0x0cE446255506E92DF41614C46F1d6df9Cc969183 

For now, know that each address is owned by a specific user or smart contract.

Mappings

We’ve already covered structs and arrays. Mappings are another way to store organized data in Solidity. They’re essentially key-value pairs, like JavaScript objects with only one entry.

// For a financial app, storing a uint that holds the user's account balance: mapping (address => uint) public accountBalance; // Or could be used to store / lookup usernames based on userId mapping (uint => string) userIdToName;

In the first example, the key is an address, and the value is a uint. In the second example, the key is a uint, and the value is a string.

Msg.sender

In Solidity, there are certain global variables that are available to all functions. One of these is msg.sender, which refers to the address of the person or smart contract who called the current function.

msg.sender will always exist when calling a function, since all Solidity function executions start with an external caller.

Here’s an example of using msg.sender and updating a mapping:

mapping (address => uint) favoriteNumber;  function setMyNumber(uint _myNumber) public {   // Update our `favoriteNumber` mapping to store `_myNumber` under `msg.sender`   favoriteNumber[msg.sender] = _myNumber;   // ^ The syntax for storing data in a mapping is just like with arrays }  function whatIsMyNumber() public view returns (uint) {   // Retrieve the value stored in the sender's address   // Will be `0` if the sender hasn't called `setMyNumber` yet   return favoriteNumber[msg.sender]; }

In this example, anyone could call setMyNumber and store a uint in our contract, which would then be mapped to their address. When they then call whatIsMyNumber, the function will return the uint that they stored.

Using msg.sender gives you the security of the Ethereum blockchain. A malicious party can only modify someone else’s data if they steal the private key associated with their Ethereum address.

Require

A require statement forces a function to throw an error and stop executing if some condition is not true.

function sayHiToVitalik(string memory _name) public returns (string memory) {   // Compares if _name equals "Vitalik". Throws an error and exits if not true.   // (Side note: Solidity doesn't have native string comparison, so we   // compare their keccak256 hashes to see if the strings are equal)   require(keccak256(abi.encodePacked(_name)) == keccak256(abi.encodePacked("Vitalik")));   // If it's true, proceed with the function:   return "Hi!"; }

If you call this function with sayHiToVitalik("Vitalik"), it will return “Hi!”. If you call it with any other input, it will throw an error and not execute.

Therefore require is quite useful for verifying certain conditions that must be true before running a function.

Inheritance

To avoid cramming a single contract full of related functions and logic, you can split your code across multiple contracts. Solidity lets you do this easily with contract inheritance.

contract Doge {   function catchphrase() public returns (string memory) {     return "So Wow CryptoDoge";   } }  contract BabyDoge is Doge {   function anotherCatchphrase() public returns (string memory) {     return "Such Moon BabyDoge";   } }

BabyDoge inherits from Doge. That means if you compile and deploy BabyDoge, it will have access to both catchphrase() and anotherCatchphrase() (and any other public functions we may define on Doge).

This can be used for logical inheritance (such as with a subclass, a Cat is an Animal). But it can also be used simply for organizing your code by grouping similar logic together into different contracts.

Import

If you need to refer to functions in another contract (e.g. if you’re creating a contract that inherits from another, or if you need to access a public function from a contract), you can use the import keyword.

import "./someothercontract.sol";  contract newContract is SomeOtherContract {  }

Storage vs. Memory (Data Location)

In Solidity, there are two locations you can store variables — in storage and in memory.

Storage refers to variables stored permanently on the blockchain. Memory variables are temporary, and are erased between external function calls to your contract. Think of it like your computer’s hard disk vs RAM.

Solidity handles these by default, so you usually don’t need to use them.

  • State variables (variables declared outside functions) are by default storage and written permanently to the blockchain.
  • Variables declared inside functions are memory and will disappear when the function call ends.

However, you need to use these keywords when dealing with structs and arrays within functions:

contract SandwichFactory {   struct Sandwich {     string name;     string status;   }    Sandwich[] sandwiches;    function eatSandwich(uint _index) public {     // Sandwich mySandwich = sandwiches[_index];      // ^ Seems pretty straightforward, but solidity will give you a warning     // telling you that you should explicitly declare `storage` or `memory` here.      // So instead, you should declare with the `storage` keyword, like:     Sandwich storage mySandwich = sandwiches[_index];     // ...in which case `mySandwich` is a pointer to `sandwiches[_index]`     // in storage, and...     mySandwich.status = "Eaten!";     // ...this will permanently change `sandwiches[_index]` on the blockchain.      // If you just want a copy, you can use `memory`:     Sandwich memory anotherSandwich = sandwiches[_index + 1];     // ...in which case `anotherSandwich` will simply be a copy of the     // data in memory, and...     anotherSandwich.status = "Eaten!";     // ...will just modify the temporary variable and have no effect     // on `sandwiches[_index + 1]`. But you can do this:     sandwiches[_index + 1] = anotherSandwich;     // ...if you want to copy the changes back into blockchain storage.   } }

Interacting With Other Contracts

For one of your contracts to talk to another contract on the blockchain that you don’t own, you need to define an interface.

Here’s a simple example. Let’s imagine that there’s a contract on the blockchain called LuckyNumber:

contract LuckyNumber {   mapping(address => uint) numbers;    function setNum(uint _num) public {     numbers[msg.sender] = _num;   }    function getNum(address _myAddress) public view returns (uint) {     return numbers[_myAddress];   } }

This is a simple contract where you can store your lucky number and associate it with your Ethereum address. Anyone could then look up your lucky number using your address.

Now let’s imagine we have a contract that wants to read the data in this contract using the public getNum function.

First, we need to define an interface of the LuckyNumber contract:

contract NumberInterface {   function getNum(address _myAddress) public view returns (uint); }

This looks a lot like we’re defining a contract, but there are a few differences:

  • We’re only declaring the functions we want to interact with — in this case getNum — and we don’t mention any of the other functions or state variables.
  • We’re not defining the function bodies. Instead of curly braces ({ and }), we’re simply ending the function declaration with a semi-colon (;).

It does look like a contract skeleton. This is how the compiler knows it’s an interface.

By including this interface in our dapp’s code our contract knows what the other contract’s functions look like, how to call them, and what sort of response to expect.

Using an Interface

Now that we’ve defined our NumberInterface (see the above section), we can use it in a contract:

contract MyContract {   address NumberInterfaceAddress = 0xab38...   // ^ The address of the FavoriteNumber contract on Ethereum   NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);   // Now `numberContract` is pointing to the other contract    function someFunction() public {     // Now we can call `getNum` from that contract:     uint num = numberContract.getNum(msg.sender);     // ...and do something with `num` here   } }

Using this pattern, our contracts can interact with any other contract on the Ethereum blockchain, as long as they expose functions as public or external.

Handling Multiple Return Values

How do we handle multiple return values? Like this:

function multipleReturns() internal returns(uint a, uint b, uint c) {   return (1, 2, 3); }  function processMultipleReturns() external {   uint a;   uint b;   uint c;   // This is how you do multiple assignment:   (a, b, c) = multipleReturns(); }  // Or if we only cared about one of the values: function getLastReturnValue() external {   uint c;   // We can just leave the other fields blank:   (,,c) = multipleReturns(); }

If Statements

if statements in Solidity look just like JavaScript:

function eatBLT(string memory sandwich) public {   // Remember with strings, we have to compare their keccak256 hashes   // to check equality   if (keccak256(abi.encodePacked(sandwich)) == keccak256(abi.encodePacked("BLT"))) {     eat();   } }

Lesson 3: Advanced Solidity Concepts

Immutability of Contracts

After you deploy a contract to Ethereum, it’s immutable. It can never be modified or updated again.

The initial code you deploy to a contract is there to stay, permanently, on the blockchain. This is one reason security is such a huge concern in Solidity. If there’s a flaw in your contract code, there’s no way for you to patch it later. You would have to tell your users to start using a different smart contract address that has the fix.

But this is also a feature of smart contracts. The code is law. If you read the code of a smart contract and verify it, you can be sure that every time you call a function it’s going to do exactly what the code says it will do. No one can later change that function and give you unexpected results.

Ownable Contracts

If you have a contract with external or public functions that let users change crucial data like Ethereum addresses, you can make contracts Ownable, giving them an owner (e.g. you) with special privileges.

It’s similar to how certain platforms only allow admins to perform certain actions.

Below is the Ownable contract taken from the OpenZeppelin Solidity library. OpenZeppelin is a library of secure and community-vetted smart contracts that you can use in your own DApps.

Here’s some more on access control from OpenZeppelin’s own documentation.

/**  * @title Ownable  * @dev The Ownable contract has an owner address, and provides basic authorization control  * functions, this simplifies the implementation of "user permissions".  */ contract Ownable {   address private _owner;    event OwnershipTransferred(     address indexed previousOwner,     address indexed newOwner   );    /**    * @dev The Ownable constructor sets the original `owner` of the contract to the sender    * account.    */   constructor() internal {     _owner = msg.sender;     emit OwnershipTransferred(address(0), _owner);   }    /**    * @return the address of the owner.    */   function owner() public view returns(address) {     return _owner;   }    /**    * @dev Throws if called by any account other than the owner.    */   modifier onlyOwner() {     require(isOwner());     _;   }    /**    * @return true if `msg.sender` is the owner of the contract.    */   function isOwner() public view returns(bool) {     return msg.sender == _owner;   }    /**    * @dev Allows the current owner to relinquish control of the contract.    * @notice Renouncing to ownership will leave the contract without an owner.    * It will not be possible to call the functions with the `onlyOwner`    * modifier anymore.    */   function renounceOwnership() public onlyOwner {     emit OwnershipTransferred(_owner, address(0));     _owner = address(0);   }    /**    * @dev Allows the current owner to transfer control of the contract to a newOwner.    * @param newOwner The address to transfer ownership to.    */   function transferOwnership(address newOwner) public onlyOwner {     _transferOwnership(newOwner);   }    /**    * @dev Transfers control of the contract to a newOwner.    * @param newOwner The address to transfer ownership to.    */   function _transferOwnership(address newOwner) internal {     require(newOwner != address(0));     emit OwnershipTransferred(_owner, newOwner);     _owner = newOwner;   } }

Some things inside this contract that we haven’t seen before:

  • Constructorsfunction Ownable() is a constructor: an optional special function with the same name as the contract. It is executed only one time, when the contract is first created.
  • Function modifiersmodifier onlyOwner(). Modifiers are kind of similar to decorators in Flask or JavaScript. They’re used to modify other functions, usually to check some requirements prior to execution.
    • In this contract, onlyOwner can be used to limit access so that only the owner of the contract can run the modified function. (Note how it’s used in renounceOwnership and transferOwnership)
  • Indexed keyword – we’ll go into this later.

onlyOwner is such a common requirement for contracts that most Solidity DApps start with a copy/paste of this Ownable contract, and then their first contract inherits from it.

Function Modifiers

Let’s delve a little deeper into function modifiers.

A function modifier uses just like a function, but uses the keyword modifier instead of the keyword function. It also can’t be called directly like a function. Instead, we can attach the modifier’s name at the end of a function definition to change that function’s behavior.

Let’s look at the onlyOwner modifier again, as well as the two functions that use it:

 modifier onlyOwner() {     require(isOwner());     _;   }   /**    * @dev Allows the current owner to relinquish control of the contract.    * @notice Renouncing to ownership will leave the contract without an owner.    * It will not be possible to call the functions with the `onlyOwner`    * modifier anymore.    */   function renounceOwnership() public onlyOwner {     emit OwnershipTransferred(_owner, address(0));     _owner = address(0);   }    /**    * @dev Allows the current owner to transfer control of the contract to a newOwner.    * @param newOwner The address to transfer ownership to.    */   function transferOwnership(address newOwner) public onlyOwner {     _transferOwnership(newOwner);   }

Notice the onlyOwner modifier on the renounceOwnership function. When you call renounceOwnership, the code inside onlyOwner executes first. Then when it hits the _; statement in onlyOwner, it goes back and executes the code inside renounceOwnership.

So while there are other ways you can use modifiers, one of the most common use-cases is to add a quick require check before a function executes.

In the case of onlyOwner, adding this modifier to a function makes it so only the owner of the contract (you, if you deployed it) can call that function.

There’s a careful balance as a developer between maintaining control over a DApp such that you can fix potential bugs, and building an owner-less platform that your users can trust to secure their data.

Gas

What’s Gas? ?

In Solidity, your users have to pay every time they execute a function on your DApp using a currency called gas. Users buy gas with Ether (the currency on Ethereum), so your users have to spend ETH in order to execute functions on your DApp.

How much gas is required to execute a function depends on how complex that function’s logic is. Each individual operation has a gas cost based roughly on how much computing resources will be required to perform that operation (e.g. writing to storage is much more expensive than adding two integers). The total gas cost of your function is the sum of the gas costs of all its individual operations.

Because running functions costs real money for your users, code optimization is much more important in Ethereum than in other programming languages. If your code is sloppy, your users are going to have to pay a premium to execute your functions — and this could add up to millions of dollars in unnecessary fees across thousands of users.

Why Is Gas Necessary?

Ethereum is like a big, slow, but extremely secure computer. When you execute a function, every single node on the network needs to run that same function to verify its output — thousands of nodes verifying every function execution is what makes Ethereum decentralized, and its data immutable and censorship-resistant.

The creators of Ethereum wanted to make sure someone couldn’t clog up the network with an infinite loop, or hog all the network resources with really intensive computations. So they made it so transactions aren’t free, and users have to pay for computation time as well as storage.

How to Save Gas by Packing Structs

Remember how there are different kinds of uints?

Generally, Solidity reserves 256 bits of storage for any uint regardless of its size. For example, uint8 and uint (uint256) will both use the same amount of gas.

Unless we’re doing this inside a struct.

If you have multiple uints inside a struct, using a smaller uint when possible will let Solidity pack these variables together to take less storage.

For example:

struct NormalStruct {   uint a;   uint b;   uint c; }  struct MiniMe {   uint32 a;   uint32 b;   uint c; }  // `mini` will cost less gas than `normal` because of struct packing NormalStruct normal = NormalStruct(10, 20, 30); MiniMe mini = MiniMe(10, 20, 30);

Here are two general strategies for saving gas with uints inside structs:

  • Use the smallest integer sub-types you can get away with.
  • Cluster identical data types together (put them next to each other) so that Solidity can minimize the required storage space. For example, a struct with fields uint c; uint32 a; uint32 b; will cost less gas than a struct with fields uint32 a; uint c; uint32 b; because the uint32 fields are clustered together.

Time Units

Solidity provides a few native unites for dealing with time.

The variable now will return the current unix timestamp of the latest block (the number of seconds that have passed since January 1st 1970). The current unix time (as of this moment on August 5, 2021) is 1628168123.

Solidity also contains the time units seconds, minutes, hours, days, weeks and years. These will convert to a uint of the number of seconds in that length of time. So 1 minutes is 60, 1 hours is 3600 (60 seconds x 60 minutes), 1 days is 86400 (24 hours x 60 minutes x 60 seconds), etc.

Here’s an example of how these time units can be useful:

uint lastUpdated;  // Set `lastUpdated` to `now` function updateTimestamp() public {   lastUpdated = now; }  // Will return `true` if 5 minutes have passed since `updateTimestamp` was // called, `false` if 5 minutes have not passed function fiveMinutesHavePassed() public view returns (bool) {   return (now >= (lastUpdated + 5 minutes)); }

Passing Structs as Arguments

You can pass a storage pointer to a struct as an argument to a private or internal function. This is useful, for example, for passing around structs between functions.

Here’s an example of the syntax (using the Zombie struct previously defined in the exercises on the site):

function _doStuff(Zombie storage _zombie) internal {   // do stuff with _zombie }

More on Function Modifiers

Previously we looked at a relatively simple function modifier:

 modifier onlyOwner() {     require(isOwner());     _;   }

However, function modifiers can also take arguments. For example:

// A mapping to store a user's age: mapping (uint => uint) public age;  // Modifier that requires this user to be older than a certain age: modifier olderThan(uint _age, uint _userId) {   require(age[_userId] >= _age);   _; }  // Must be older than 16 to drive a car (in the US, at least). // We can call the `olderThan` modifier with arguments like so: function driveCar(uint _userId) public olderThan(16, _userId) {   // Some function logic }

Notice that…

  • The olderThan modifier takes arguments just like a function does
  • The driveCar function passes its arguments to the modifier

add table of contents


Cryptozombies Notes

概述加密僵尸在线课程中涉及的核心坚固性概念。由课文和原始摘要组成

查看Solidity备忘单,快速总结其中一些概念

Resources

  • Official Solidity Cheatsheet

Table of Contents

  • Lesson 1: Solidity Basics
    • Contracts
    • State Variables And Integers
      • State Variables
      • Unsigned Integers: uint
    • Math Operations
    • Structs
    • Arrays
    • Function Declarations
      • Reference Types
    • Working with Structs And Arrays
    • Private And Public Functions
    • Internal And External Functions
    • More on Functions
      • Return values
      • Functions modifiers
    • Keccak256 And Typecasting
      • keccak256
      • Typecasting
    • Events
    • Web3.js
  • Lesson 2: Beyond Basics
    • Mappings And Addresses
      • Addresses
      • Mappings
    • Msg.sender
    • Require
    • Inheritance
    • Import
    • Storage vs. Memory (Data Location)
    • Interacting With Other Contracts
    • Using an Interface
    • Handling Multiple Return Values
    • If Statements
  • Lesson 3: Advanced Solidity Concepts
    • Immutability of Contracts
    • Ownable Contracts
    • Function Modifiers
    • Gas
      • What’s Gas?
      • Why Is Gas Necessary?
      • How to Save Gas by Packing Structs
    • Time Units
    • Passing Structs as Arguments
    • More on Function Modifiers

Lesson 1: Solidity Basics

Contracts

Solidity的代码封装在合同中。契约是以太坊eth应用程序的基本构建块——所有变量和函数都属于契约,这将是您所有项目的起点

名为HelloWorld的空契约如下所示:

contract HelloWorld {  }

所有solidity源代码都应该以“version pragma”开头,这是该代码应该使用的solidity编译器版本的声明。这是为了防止将来的编译器版本可能引入的更改会破坏您的代码

下面是一个智能合约的示例,任何版本为0.7.3或更高版本的编译器都将编译该智能合约:

pragma solidity >=0.7.3;  contract HelloWorld {  }

State Variables And Integers

State Variables

状态变量永久存储在合约存储中。它们被写入以太坊eth区块链blockchain。永远(与存储在内存中的数据不同(我们将在后面讨论。)

当我们使用智能合约时,我们存储和访问来自区块链blockchain而非传统数据库的数据。因此,在合同中使用状态变量就相当于将这些信息存储在数据库中

contract Example {   // This will be stored permanently in the blockchain   uint myUnsignedInteger = 100; }

在这个示例契约中,我们创建了一个名为myUnsignedInteger的uint,并将其设置为100。

Unsigned Integers: uint

uint数据类型是一个无符号整数,这意味着它的值必须是非负的。对于有符号整数,可以使用int类型

uint实际上相当于uint256,一个256位无符号整数。您还可以使用位数较少的UINT(uint8、uint16、uint32…)以提高成本效率,我们稍后也将讨论这一点

Math Operations

Solidity支持与编程语言相同的数学运算:

  • Addition: x + y
  • Subtraction: x – y,
  • Multiplication: x * y
  • Division: x / y
  • Modulus / remainder: x % y
  • Exponents: x ** 2

Structs

结构是一种具有多个属性的复杂数据类型:

struct Person {   uint age;   string name; }

Arrays

Solidity中的数组与JavaScript中的数组类似

Solidity有两种类型的数组:

  • Fixed arrays: Have fixed lengths.
  • Dynamic arrays: Have no fixed size and can contain any number of elements.
// Array with a fixed length of 2 elements: uint[2] fixedArray; // another fixed Array, can contain 5 strings: string[5] stringArray; // a dynamic Array - has no fixed size, can keep growing: uint[] dynamicArray;

您还可以创建结构数组。还记得上一课的Person结构吗

Person[] people; // dynamic Array, we can keep adding to it

由于状态变量永久存储在区块链blockchain中,因此创建这样的结构动态数组对于在合同中存储结构化数据非常有用,就像数据库一样,记得吗

Public Arrays

可以将数组声明为public,Solidity将自动为其创建一个getter方法。语法如下所示:

Person[] public people;

其他契约可以从此数组中读取,但不能写入。这使它成为在合同中存储公共数据的有用模式

Function Declarations

函数声明的完整性如下所示:

function eatHamburgers(string memory _name, uint _amount) public {  }

上述函数eatHamburgers接受两个参数:

  • A string called _name
  • A uint called _amount

请注意,函数是公共的,这意味着可以在内部或通过消息(外部)调用它

为什么这两个参数都以下划线开头?按照惯例,函数参数变量名以下划线开头(例如,用_名称代替名称),以区别于全局变量

Reference Types

在Solidity中,字符串是引用类型,而uint是值类型

有两种方法可以将参数传递给Solidity函数:

  • By value – the solidity creates a new copy of the param’s value and passes it to the function. The value of the original parameter is not changed.
  • By reference – The functions is called with a reference to the original value. If the function changes the value of the variable received, the original variable is also mutated.

Working with Structs And Arrays

让我们回顾一下前面创建的Person结构:

struct Person {   uint age;   string name; }  Person[] public people;

现在,让我们创建新的Person并将其添加到Person数组:

// create a New Person: Person satoshi = Person(172, "Satoshi");  // Add that person to the Array: people.push(satoshi);

让我们在一行中完成这一切:

people.push(Person(172, "Satoshi"));

注意array.push()将项目添加到数组的末尾

uint[] numbers; numbers.push(5); numbers.push(10); numbers.push(15); // numbers is now equal to [5, 10, 15]

Private And Public Functions

在Solidity中,函数默认为公共函数。这意味着任何人(或任何其他合同)都可以调用合同的函数并执行其代码

这可能会造成安全问题,因为它可能使您的合同容易受到攻击。默认情况下,将函数设置为私有(这意味着它们只能从同一个契约中访问),然后如果要向所有人公开函数,则只将其标记为公共,这是一种很好的做法

下面是一个如何声明私有函数的示例:

uint[] numbers;  function _addToArray(uint _number) private {   numbers.push(_number); }

只有同一契约中的函数才能调用_addToArray并添加到数字数组中

注意“addToArray”开头的u。与函数参数一样,通常以下划线开始私有函数

Internal And External Functions

Solidity还为我们提供了另外两种功能可见性:内部和外部

internal与private相同,但有一个好处:继承自此合同的合同也可以访问它

external类似于public,但只能在合同之外调用外部函数。与public不同,它们不能被该契约中的其他函数调用

More on Functions

Return values

如果函数返回值,则在声明函数时还应指定类型:

string greeting = "What's up dog";  function sayHello() public returns (string memory) {   return greeting; }

Function modifiers

上述函数(sayHello)不修改状态-它只返回一个预先存在的状态变量。因此,它可以声明为视图函数:

string greeting = "What's up dog";  function sayHello() public view returns (string memory) {   return greeting; }

您还可以编写纯函数,这些函数不访问应用程序中的任何数据

function _multiply(uint a, uint b) private pure returns (uint) {   return a * b; }

Keccak256 And Typecasting

keccak256

注意,keccak256需要一个字节类型的参数。这要求我们在调用keccak256之前“打包”参数:

Solidity允许您在数据类型之间进行转换。例如:

//6e91ec6b618bb462a4a6ee5aa2cb0e9cf30f7a052bb467b0ba58b8748c00d2e5 keccak256(abi.encodePacked("aaaab")); //b1f078126895a1424524de5321b339ab00408010b7cf0e6ed451514981e58aa9 keccak256(abi.encodePacked("aaaac"));

Typecasting

事件允许您的合同与应用程序的前端进行通信,它可以监听特定事件并在事件发生时执行操作

uint8 a = 5; uint b = 6; // throws an error because a * b returns a uint, not uint8: uint8 c = a * b; // we have to typecast b as a uint8 to make it work: uint8 c = a * uint8(b);

Events

前端上此事件的JavaScript实现可能如下所示:

// declare the event event IntegersAdded(uint x, uint y, uint result);  function add(uint _x, uint _y) public returns (uint) {   uint result = _x + _y;   // fire an event to let the app know the function was called:   emit IntegersAdded(_x, _y, result);   return result; }

以太坊eth有一个名为Web3.js的JavaScript库

YourContract.IntegersAdded(function (error, result) {   // do something with result });

Web3.js

稍后我们将讨论这个问题,但这里有一个示例,说明前端如何使用库与智能合约交互

合同

前端,使用Web3.js

pragma solidity ^0.4.25;  contract ZombieFactory {      event NewZombie(uint zombieId, string name, uint dna);      uint dnaDigits = 16;     uint dnaModulus = 10 ** dnaDigits;      struct Zombie {         string name;         uint dna;     }      Zombie[] public zombies;      function _createZombie(string _name, uint _dna) private {         uint id = zombies.push(Zombie(_name, _dna)) - 1;         emit NewZombie(id, _name, _dna);     }      function _generateRandomDna(string _str) private view returns (uint) {         uint rand = uint(keccak256(abi.encodePacked(_str)));         return rand % dnaModulus;     }      function createRandomZombie(string _name) public {         uint randDna = _generateRandomDna(_name);         _createZombie(_name, randDna);     }  }

以太坊eth区块链blockchain由账户(如银行账户)组成。每个账户都有一个账户余额。您可以向其他帐户发送和接收以太付款(就像银行帐户可以将资金电汇到其他帐户并从其他帐户接收转账)

// Here's how we would access our contract: var abi = /* abi generated by the compiler */ var ZombieFactoryContract = web3.eth.contract(abi) var contractAddress = /* our contract address on Ethereum after deploying */ var ZombieFactory = ZombieFactoryContract.at(contractAddress) // `ZombieFactory` has access to our contract's public functions and events  // some sort of event listener to take the text input: $("#ourButton").click(function(e) {   var name = $("#nameInput").val()   // Call our contract's `createRandomZombie` function:   ZombieFactory.createRandomZombie(name) })  // Listen for the `NewZombie` event, and update the UI var event = ZombieFactory.NewZombie(function(error, result) {   if (error) return   generateZombie(result.zombieId, result.name, result.dna) })  // take the Zombie dna, and update our image function generateZombie(id, name, dna) {   let dnaStr = String(dna)   // pad DNA with leading zeroes if it's less than 16 characters   while (dnaStr.length < 16)     dnaStr = "0" + dnaStr    let zombieDetails = {     // first 2 digits make up the head. We have 7 possible heads, so % 7     // to get a number 0 - 6, then add 1 to make it 1 - 7. Then we have 7     // image files named "head1.png" through "head7.png" we load based on     // this number:     headChoice: dnaStr.substring(0, 2) % 7 + 1,     // 2nd 2 digits make up the eyes, 11 variations:     eyeChoice: dnaStr.substring(2, 4) % 11 + 1,     // 6 variations of shirts:     shirtChoice: dnaStr.substring(4, 6) % 6 + 1,     // last 6 digits control color. Updated using CSS filter: hue-rotate     // which has 360 degrees:     skinColorChoice: parseInt(dnaStr.substring(6, 8) / 100 * 360),     eyeColorChoice: parseInt(dnaStr.substring(8, 10) / 100 * 360),     clothesColorChoice: parseInt(dnaStr.substring(10, 12) / 100 * 360),     zombieName: name,     zombieDescription: "A Level 1 CryptoZombie",   }   return zombieDetails }

Lesson 2: Beyond Basics

Mappings And Addresses

Addresses

每个帐户都有一个地址-类似于一个帐号。看起来是这样的:

现在,要知道每个地址都属于特定的用户或智能合约

0x0cE446255506E92DF41614C46F1d6df9Cc969183 

我们已经介绍了结构和数组。映射是另一种可靠地存储有组织数据的方法。它们本质上是键值对,就像只有一个条目的JavaScript对象一样

Mappings

在第一个示例中,键是地址,值是uint。在第二个示例中,键是uint,值是字符串

// For a financial app, storing a uint that holds the user's account balance: mapping (address => uint) public accountBalance; // Or could be used to store / lookup usernames based on userId mapping (uint => string) userIdToName;

在Solidity中,有一些全局变量可用于所有函数。其中一个是msg.sender,它指调用当前函数的人员或智能合约的地址

Msg.sender

下面是一个使用msg.sender并更新映射的示例:

在这个示例中,任何人都可以调用setMyNumber并在我们的合同中存储uint,然后将uint映射到他们的地址。当他们调用whatIsMyNumber时,函数将返回他们存储的uint

使用msg.sender为您提供以太坊eth区块链blockchain的安全性。恶意方只有在窃取与其以太坊eth地址相关联的私钥时才能修改其他人的数据

mapping (address => uint) favoriteNumber;  function setMyNumber(uint _myNumber) public {   // Update our `favoriteNumber` mapping to store `_myNumber` under `msg.sender`   favoriteNumber[msg.sender] = _myNumber;   // ^ The syntax for storing data in a mapping is just like with arrays }  function whatIsMyNumber() public view returns (uint) {   // Retrieve the value stored in the sender's address   // Will be `0` if the sender hasn't called `setMyNumber` yet   return favoriteNumber[msg.sender]; }

require语句强制函数抛出错误,并在某些条件不成立时停止执行

如果用sayHiToVitalik(“Vitalik”)调用此函数,它将返回“Hi!”。如果您使用任何其他输入调用它,它将抛出错误而不执行

Require

因此,require对于在运行函数之前验证某些必须为true的条件非常有用

function sayHiToVitalik(string memory _name) public returns (string memory) {   // Compares if _name equals "Vitalik". Throws an error and exits if not true.   // (Side note: Solidity doesn't have native string comparison, so we   // compare their keccak256 hashes to see if the strings are equal)   require(keccak256(abi.encodePacked(_name)) == keccak256(abi.encodePacked("Vitalik")));   // If it's true, proceed with the function:   return "Hi!"; }

为了避免将一个契约塞满相关函数和逻辑,您可以将代码拆分为多个契约。Solidity允许您通过契约继承轻松实现这一点

BabyDoge继承自Doge。这意味着,如果编译和部署BabyDoge,它将可以访问catchphrase()和另一个catchphrase()(以及我们可能在Doge上定义的任何其他公共函数)

Inheritance

这可用于逻辑继承(例如,对于子类,猫是动物)。但它也可以简单地用于通过将相似的逻辑分组到不同的契约中来组织代码

contract Doge {   function catchphrase() public returns (string memory) {     return "So Wow CryptoDoge";   } }  contract BabyDoge is Doge {   function anotherCatchphrase() public returns (string memory) {     return "Such Moon BabyDoge";   } }

如果您需要引用另一个合约中的函数(例如,如果您正在创建从另一个合约继承的合约,或者如果您需要从合约访问公共函数),则可以使用导入关键字

在Solidity中,有两个位置可以存储变量-在存储器和内存中

Import

存储是指永久存储在区块链blockchain上的变量。内存变量是临时的,在对合约的外部函数调用之间会被擦除。把它想象成你电脑的硬盘和RAM

import "./someothercontract.sol";  contract newContract is SomeOtherContract {  }

Storage vs. Memory (Data Location)

默认情况下,Solidity会处理这些属性,因此通常不需要使用它们

但是,在处理函数中的结构和数组时,您需要使用这些关键字:

要让您的一个合约与区块链blockchain上您不拥有的另一个合约对话,您需要定义一个接口

  • State variables (variables declared outside functions) are by default storage and written permanently to the blockchain.
  • Variables declared inside functions are memory and will disappear when the function call ends.

这里有一个简单的例子。让我们假设区块链blockchain上有一个名为LuckyNumber的合同:

contract SandwichFactory {   struct Sandwich {     string name;     string status;   }    Sandwich[] sandwiches;    function eatSandwich(uint _index) public {     // Sandwich mySandwich = sandwiches[_index];      // ^ Seems pretty straightforward, but solidity will give you a warning     // telling you that you should explicitly declare `storage` or `memory` here.      // So instead, you should declare with the `storage` keyword, like:     Sandwich storage mySandwich = sandwiches[_index];     // ...in which case `mySandwich` is a pointer to `sandwiches[_index]`     // in storage, and...     mySandwich.status = "Eaten!";     // ...this will permanently change `sandwiches[_index]` on the blockchain.      // If you just want a copy, you can use `memory`:     Sandwich memory anotherSandwich = sandwiches[_index + 1];     // ...in which case `anotherSandwich` will simply be a copy of the     // data in memory, and...     anotherSandwich.status = "Eaten!";     // ...will just modify the temporary variable and have no effect     // on `sandwiches[_index + 1]`. But you can do this:     sandwiches[_index + 1] = anotherSandwich;     // ...if you want to copy the changes back into blockchain storage.   } }

Interacting With Other Contracts

这是一个简单的合同,您可以存储您的幸运号码并将其与以太坊eth地址关联。任何人都可以使用你的地址查找你的幸运号码

现在,让我们假设我们有一个契约,它希望使用public getNum函数读取该契约中的数据

contract LuckyNumber {   mapping(address => uint) numbers;    function setNum(uint _num) public {     numbers[msg.sender] = _num;   }    function getNum(address _myAddress) public view returns (uint) {     return numbers[_myAddress];   } }

首先,我们需要定义LuckyNumber合同的接口:

这看起来很像我们定义的合同,但有一些区别:

它看起来确实像合同框架。编译器就是这样知道它是一个接口的

contract NumberInterface {   function getNum(address _myAddress) public view returns (uint); }

通过在我们的dapp代码中包含此接口,我们的公司

  • We’re only declaring the functions we want to interact with — in this case getNum — and we don’t mention any of the other functions or state variables.
  • We’re not defining the function bodies. Instead of curly braces ({ and }), we’re simply ending the function declaration with a semi-colon (;).

It does look like a contract skeleton. This is how the compiler knows it’s an interface.

By including this interface in our dapp’s code our contract knows what the other contract’s functions look like, how to call them, and what sort of response to expect.

Using an Interface

Now that we’ve defined our NumberInterface (see the above section), we can use it in a contract:

contract MyContract {   address NumberInterfaceAddress = 0xab38...   // ^ The address of the FavoriteNumber contract on Ethereum   NumberInterface numberContract = NumberInterface(NumberInterfaceAddress);   // Now `numberContract` is pointing to the other contract    function someFunction() public {     // Now we can call `getNum` from that contract:     uint num = numberContract.getNum(msg.sender);     // ...and do something with `num` here   } }

Using this pattern, our contracts can interact with any other contract on the Ethereum blockchain, as long as they expose functions as public or external.

Handling Multiple Return Values

How do we handle multiple return values? Like this:

function multipleReturns() internal returns(uint a, uint b, uint c) {   return (1, 2, 3); }  function processMultipleReturns() external {   uint a;   uint b;   uint c;   // This is how you do multiple assignment:   (a, b, c) = multipleReturns(); }  // Or if we only cared about one of the values: function getLastReturnValue() external {   uint c;   // We can just leave the other fields blank:   (,,c) = multipleReturns(); }

If Statements

if statements in Solidity look just like JavaScript:

function eatBLT(string memory sandwich) public {   // Remember with strings, we have to compare their keccak256 hashes   // to check equality   if (keccak256(abi.encodePacked(sandwich)) == keccak256(abi.encodePacked("BLT"))) {     eat();   } }

Lesson 3: Advanced Solidity Concepts

Immutability of Contracts

After you deploy a contract to Ethereum, it’s immutable. It can never be modified or updated again.

The initial code you deploy to a contract is there to stay, permanently, on the blockchain. This is one reason security is such a huge concern in Solidity. If there’s a flaw in your contract code, there’s no way for you to patch it later. You would have to tell your users to start using a different smart contract address that has the fix.

But this is also a feature of smart contracts. The code is law. If you read the code of a smart contract and verify it, you can be sure that every time you call a function it’s going to do exactly what the code says it will do. No one can later change that function and give you unexpected results.

Ownable Contracts

If you have a contract with external or public functions that let users change crucial data like Ethereum addresses, you can make contracts Ownable, giving them an owner (e.g. you) with special privileges.

It’s similar to how certain platforms only allow admins to perform certain actions.

Below is the Ownable contract taken from the OpenZeppelin Solidity library. OpenZeppelin is a library of secure and community-vetted smart contracts that you can use in your own DApps.

Here’s some more on access control from OpenZeppelin’s own documentation.

/**  * @title Ownable  * @dev The Ownable contract has an owner address, and provides basic authorization control  * functions, this simplifies the implementation of "user permissions".  */ contract Ownable {   address private _owner;    event OwnershipTransferred(     address indexed previousOwner,     address indexed newOwner   );    /**    * @dev The Ownable constructor sets the original `owner` of the contract to the sender    * account.    */   constructor() internal {     _owner = msg.sender;     emit OwnershipTransferred(address(0), _owner);   }    /**    * @return the address of the owner.    */   function owner() public view returns(address) {     return _owner;   }    /**    * @dev Throws if called by any account other than the owner.    */   modifier onlyOwner() {     require(isOwner());     _;   }    /**    * @return true if `msg.sender` is the owner of the contract.    */   function isOwner() public view returns(bool) {     return msg.sender == _owner;   }    /**    * @dev Allows the current owner to relinquish control of the contract.    * @notice Renouncing to ownership will leave the contract without an owner.    * It will not be possible to call the functions with the `onlyOwner`    * modifier anymore.    */   function renounceOwnership() public onlyOwner {     emit OwnershipTransferred(_owner, address(0));     _owner = address(0);   }    /**    * @dev Allows the current owner to transfer control of the contract to a newOwner.    * @param newOwner The address to transfer ownership to.    */   function transferOwnership(address newOwner) public onlyOwner {     _transferOwnership(newOwner);   }    /**    * @dev Transfers control of the contract to a newOwner.    * @param newOwner The address to transfer ownership to.    */   function _transferOwnership(address newOwner) internal {     require(newOwner != address(0));     emit OwnershipTransferred(_owner, newOwner);     _owner = newOwner;   } }

Some things inside this contract that we haven’t seen before:

  • Constructorsfunction Ownable() is a constructor: an optional special function with the same name as the contract. It is executed only one time, when the contract is first created.
  • Function modifiersmodifier onlyOwner(). Modifiers are kind of similar to decorators in Flask or JavaScript. They’re used to modify other functions, usually to check some requirements prior to execution.
    • In this contract, onlyOwner can be used to limit access so that only the owner of the contract can run the modified function. (Note how it’s used in renounceOwnership and transferOwnership)
  • Indexed keyword – we’ll go into this later.

onlyOwner is such a common requirement for contracts that most Solidity DApps start with a copy/paste of this Ownable contract, and then their first contract inherits from it.

Function Modifiers

Let’s delve a little deeper into function modifiers.

A function modifier uses just like a function, but uses the keyword modifier instead of the keyword function. It also can’t be called directly like a function. Instead, we can attach the modifier’s name at the end of a function definition to change that function’s behavior.

Let’s look at the onlyOwner modifier again, as well as the two functions that use it:

 modifier onlyOwner() {     require(isOwner());     _;   }   /**    * @dev Allows the current owner to relinquish control of the contract.    * @notice Renouncing to ownership will leave the contract without an owner.    * It will not be possible to call the functions with the `onlyOwner`    * modifier anymore.    */   function renounceOwnership() public onlyOwner {     emit OwnershipTransferred(_owner, address(0));     _owner = address(0);   }    /**    * @dev Allows the current owner to transfer control of the contract to a newOwner.    * @param newOwner The address to transfer ownership to.    */   function transferOwnership(address newOwner) public onlyOwner {     _transferOwnership(newOwner);   }

Notice the onlyOwner modifier on the renounceOwnership function. When you call renounceOwnership, the code inside onlyOwner executes first. Then when it hits the _; statement in onlyOwner, it goes back and executes the code inside renounceOwnership.

So while there are other ways you can use modifiers, one of the most common use-cases is to add a quick require check before a function executes.

In the case of onlyOwner, adding this modifier to a function makes it so only the owner of the contract (you, if you deployed it) can call that function.

There’s a careful balance as a developer between maintaining control over a DApp such that you can fix potential bugs, and building an owner-less platform that your users can trust to secure their data.

Gas

What’s Gas? ?

In Solidity, your users have to pay every time they execute a function on your DApp using a currency called gas. Users buy gas with Ether (the currency on Ethereum), so your users have to spend ETH in order to execute functions on your DApp.

How much gas is required to execute a function depends on how complex that function’s logic is. Each individual operation has a gas cost based roughly on how much computing resources will be required to perform that operation (e.g. writing to storage is much more expensive than adding two integers). The total gas cost of your function is the sum of the gas costs of all its individual operations.

Because running functions costs real money for your users, code optimization is much more important in Ethereum than in other programming languages. If your code is sloppy, your users are going to have to pay a premium to execute your functions — and this could add up to millions of dollars in unnecessary fees across thousands of users.

Why Is Gas Necessary?

Ethereum is like a big, slow, but extremely secure computer. When you execute a function, every single node on the network needs to run that same function to verify its output — thousands of nodes verifying every function execution is what makes Ethereum decentralized, and its data immutable and censorship-resistant.

The creators of Ethereum wanted to make sure someone couldn’t clog up the network with an infinite loop, or hog all the network resources with really intensive computations. So they made it so transactions aren’t free, and users have to pay for computation time as well as storage.

How to Save Gas by Packing Structs

Remember how there are different kinds of uints?

Generally, Solidity reserves 256 bits of storage for any uint regardless of its size. For example, uint8 and uint (uint256) will both use the same amount of gas.

Unless we’re doing this inside a struct.

If you have multiple uints inside a struct, using a smaller uint when possible will let Solidity pack these variables together to take less storage.

For example:

struct NormalStruct {   uint a;   uint b;   uint c; }  struct MiniMe {   uint32 a;   uint32 b;   uint c; }  // `mini` will cost less gas than `normal` because of struct packing NormalStruct normal = NormalStruct(10, 20, 30); MiniMe mini = MiniMe(10, 20, 30);

Here are two general strategies for saving gas with uints inside structs:

  • Use the smallest integer sub-types you can get away with.
  • Cluster identical data types together (put them next to each other) so that Solidity can minimize the required storage space. For example, a struct with fields uint c; uint32 a; uint32 b; will cost less gas than a struct with fields uint32 a; uint c; uint32 b; because the uint32 fields are clustered together.

Time Units

Solidity provides a few native unites for dealing with time.

The variable now will return the current unix timestamp of the latest block (the number of seconds that have passed since January 1st 1970). The current unix time (as of this moment on August 5, 2021) is 1628168123.

Solidity also contains the time units seconds, minutes, hours, days, weeks and years. These will convert to a uint of the number of seconds in that length of time. So 1 minutes is 60, 1 hours is 3600 (60 seconds x 60 minutes), 1 days is 86400 (24 hours x 60 minutes x 60 seconds), etc.

Here’s an example of how these time units can be useful:

uint lastUpdated;  // Set `lastUpdated` to `now` function updateTimestamp() public {   lastUpdated = now; }  // Will return `true` if 5 minutes have passed since `updateTimestamp` was // called, `false` if 5 minutes have not passed function fiveMinutesHavePassed() public view returns (bool) {   return (now >= (lastUpdated + 5 minutes)); }

Passing Structs as Arguments

You can pass a storage pointer to a struct as an argument to a private or internal function. This is useful, for example, for passing around structs between functions.

Here’s an example of the syntax (using the Zombie struct previously defined in the exercises on the site):

function _doStuff(Zombie storage _zombie) internal {   // do stuff with _zombie }

More on Function Modifiers

Previously we looked at a relatively simple function modifier:

 modifier onlyOwner() {     require(isOwner());     _;   }

However, function modifiers can also take arguments. For example:

// A mapping to store a user's age: mapping (uint => uint) public age;  // Modifier that requires this user to be older than a certain age: modifier olderThan(uint _age, uint _userId) {   require(age[_userId] >= _age);   _; }  // Must be older than 16 to drive a car (in the US, at least). // We can call the `olderThan` modifier with arguments like so: function driveCar(uint _userId) public olderThan(16, _userId) {   // Some function logic }

Notice that…

  • The olderThan modifier takes arguments just like a function does
  • The driveCar function passes its arguments to the modifier

add table of contents

部分转自网络,侵权联系删除区块链源码网

www.interchains.cc

https://www.interchains.cc/23548.html

区块链毕设网(www.interchains.cc)全网最靠谱的原创区块链毕设代做网站 部分资料来自网络,侵权联系删除! 最全最大的区块链源码站 ! QQ3039046426
区块链知识分享网, 以太坊dapp资源网, 区块链教程, fabric教程下载, 区块链书籍下载, 区块链资料下载, 区块链视频教程下载, 区块链基础教程, 区块链入门教程, 区块链资源 » Cryptozombies Notes – 加密僵尸笔记区块链毕设代写

提供最优质的资源集合

立即查看 了解详情