Solidity基本概念学习2

发布时间 2023-12-06 15:02:51作者: 芜光

文档: https://solidity-by-example.org/
视频教程: https://www.youtube.com/watch?v=xv9OmztShIw&list=PLO5VPQH6OWdVQwpQfw9rZ67O6Pjfo6q-p

说明

看视频没注意有文档, 前面写了一篇好多废话, 之后结合文档+视频去做笔记和写代码, 做记录...

常量(constant)

常量是不能修改的变量。它们的值是硬编码的,使用常量可以节省gas成本。

不可变变量(imutable)

不可变变量就像常量。不可变变量的值可以在构造函数内部设置,但之后不能修改。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract ConstantsAndImutable {
    // coding convention to uppercase constant variables
    address public constant MY_ADDRESS = 0x777788889999AaAAbBbbCcccddDdeeeEfFFfCcCc;
    uint public constant MY_UINT = 123;

    // coding convention to uppercase constant variables
    address public immutable MY_ADDRESS2;
    uint public immutable MY_UINT2;

    constructor(uint _myUint) {
        MY_ADDRESS2 = msg.sender;
        MY_UINT2 = _myUint;
    }
}

注意这种带构造器的合约需要填入初始值才能部署, 不然会报错:
creation of Constants errored: Error encoding arguments: Error: invalid BigNumber string (argument="value", value="", code=INVALID_ARGUMENT, version=bignumber/5.7.0)
img
从显示来看的话没有太大区别:
img

对状态变量的读写

要编写或更新状态变量,需要发送一个交易(transaction)。
另一方面,可以免费读取状态变量,而无需支付任何交易费用。
实际上前面已经做过类似的操作了. 修改一下案例.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;
contract StateVariable {
    uint public stateval = 123;
    function get() view external returns(uint)  {
        return stateval;
    }
    function set(uint myval) external {
        stateval = myval;
    }
}

如果是用的MetaMask的话会对这个支付费用比较敏感. 每次执行都需要用户去确认交易. 而Remix vm提供的测试号都是默认直接执行交易了.
执行前后
问题: 函数也可以用public修饰, 那么publicexternal是有什么区别?

货币单位ether和wei

交易(transaction)需要用ether支付。
类似于一美元等于100美分的方式,1个以太等于$10^{18}$ WEI。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract EtherUnits {
    uint public oneWei = 1 wei;
    // 1 wei is equal to 1
    bool public isOneWei = 1 wei == 1; // true

    uint public oneEther = 1 ether;
    // 1 ether is equal to 10^18 wei
    bool public isOneEther = 1 ether == 1e18; // true
}

gas fee (燃气费)

你需要支付多少以太币进行一笔交易?
你支付的以太币数量为 gas spent(已消耗的燃气)* gas price(每单位燃气愿意支付的以太币),其中:

  • gas 是计算的单位。
  • gas spent 是交易中使用的总燃气费用。
  • gas price 是你愿意为每单位燃气支付的以太币。
    具有更高gas price的交易在区块中具有更高的优先级。未使用的燃气将被退还。
    燃气限制
    对于你可以使用的燃气量有两个上限:
  • gas limit(你愿意在交易中使用的最大燃气量,由你设置)
  • block gas limit(区块中允许的最大燃气量,由网络设置)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
// 用死循环去测试gas的返还
contract Gas {
    uint public i = 0;
    // Using up all of the gas that you send causes your transaction to fail.
    // State changes are undone.
    // Gas spent are not refunded.
    function forever() public {
        // Here we run a loop until all of the gas are spent
        // and the transaction fails
        while (true) {
            i += 1;
        }
    }
}

执行forever后

if/else, 三元运算符

跟Java和C一样的.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract IfElse {
    function foo(uint x) public pure returns (uint) {
        if (x < 10) {
            return 0;
        } else if (x < 20) {
            return 1;
        } else {
            return 2;
        }
    }

    function ternary(uint _x) public pure returns (uint) {
        // if (_x < 10) {
        //     return 1;
        // }
        // return 2;

        // shorthand way to write if / else statement
        // the "?" operator is called the ternary operator
        return _x < 10 ? 1 : 2;
    }
}

for, while, do-while

循环结构也跟Java, C类似

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Loop {
    function loop() public {
        // for loop
        for (uint i = 0; i < 10; i++) {
            if (i == 3) {
                // Skip to next iteration with continue
                continue;
            }
            if (i == 5) {
                // Exit loop with break
                break;
            }
        }

        // while loop
        uint j;
        while (j < 10) {
            j++;
        }
    }
}

Mapping

使用语法(keyType => valueType)创建映射。
keyType可以是任何内置值类型、字节、字符串或任何合约。valueType可以是任何类型,包括另一个MappingArrayMapping是不可迭代的。
Mapping貌似不能作为返回类型, 会报错:
TypeError: Data location must be "memory" or "calldata" for return parameter in function, but none was given.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8;

contract Mapping {
    mapping (address => uint) public myMap; // 用于观察
    // Mapping 总会返回值.
    // If the value was never set, it will return the default value.
    function get(address key) public view returns(uint) {
        return myMap[key]; // 2885 gas
    }
    function set(address key, uint val) public {
        myMap[key] = val; // 22854 gas
    }
    function remove(address key) public {
        delete myMap[key]; // 5554 gas
    }
}
contract NestedMapping {
    mapping (address => mapping(uint => bool)) public nestedMap;
    function get(address addr, uint _i) public view returns(bool){
        return nestedMap[addr][_i]; // 3178 gas
    }
    function set(address addr, uint _i, bool _b) public {
        nestedMap[addr][_i] = _b;  // 25217 gas
    }
    function remove(address addr, uint _i) public {
        delete nestedMap[addr][_i]; // 25060 gas
    }
}

随便找个地址测着玩就行, 按照两个合约和上面提到的报错来看, 我们没法一次获取整个Mapping的值?
img

Array

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract Array {
    // Several ways to initialize an array
    uint[] public arr;
    uint[] public arr2 = [1, 2, 3];
    // Fixed sized array, all elements initialize to 0
    uint[10] public myFixedSizeArr;

    function get(uint i) public view returns (uint) {
        return arr[i];
    }

    // Solidity can return the entire array.
    // But this function should be avoided for
    // arrays that can grow indefinitely in length.
    function getArr() public view returns (uint[] memory) {
        return arr;
    }

    function push(uint i) public {
        // Append to array
        // This will increase the array length by 1.
        arr.push(i);
    }

    function pop() public {
        // Remove last element from array
        // This will decrease the array length by 1
        arr.pop();
    }

    function getLength() public view returns (uint) {
        return arr.length;
    }

    function remove(uint index) public {
        // Delete does not change the array length.
        // It resets the value at index to it's default value,
        // in this case 0
        delete arr[index];
    }

    function examples() external pure  {
        // create array in memory, only fixed size can be created
        uint[] memory a = new uint[](5);
    }
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract ArrayRemoveByShifting {
    // [1, 2, 3] -- remove(1) --> [1, 3, 3] --> [1, 3]
    // [1, 2, 3, 4, 5, 6] -- remove(2) --> [1, 2, 4, 5, 6, 6] --> [1, 2, 4, 5, 6]
    // [1, 2, 3, 4, 5, 6] -- remove(0) --> [2, 3, 4, 5, 6, 6] --> [2, 3, 4, 5, 6]
    // [1] -- remove(0) --> [1] --> []

    uint[] public arr;

    function remove(uint _index) public {
        require(_index < arr.length, "index out of bound");

        for (uint i = _index; i < arr.length - 1; i++) {
            arr[i] = arr[i + 1];
        }
        arr.pop();
    }

    function test() external {
        arr = [1, 2, 3, 4, 5];
        remove(2);
        // [1, 2, 4, 5]
        assert(arr[0] == 1);
        assert(arr[1] == 2);
        assert(arr[2] == 4);
        assert(arr[3] == 5);
        assert(arr.length == 4);

        arr = [1];
        remove(0);
        // []
        assert(arr.length == 0);
    }
}

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract ArrayReplaceFromEnd {
    uint[] public arr;

    // Deleting an element creates a gap in the array.
    // One trick to keep the array compact is to
    // move the last element into the place to delete.
    function remove(uint index) public {
        // Move the last element into the place to delete
        arr[index] = arr[arr.length - 1];
        // Remove the last element
        arr.pop();
    }

    function test() public {
        arr = [1, 2, 3, 4];

        remove(1);
        // [1, 4, 3]
        assert(arr.length == 3);
        assert(arr[0] == 1);
        assert(arr[1] == 4);
        assert(arr[2] == 3);

        remove(2);
        // [1, 4]
        assert(arr.length == 2);
        assert(arr[0] == 1);
        assert(arr[1] == 4);
    }
}

示例代码自己部署自己玩.