首页 > 其他分享 >Solidity基本语法学习5

Solidity基本语法学习5

时间:2023-12-13 19:14:20浏览次数:31  
标签:function 学习 Solidity value 语法 payable uint address public

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

说明

本文内容payable, Sending Ether (transfer, send, call), Fallback, Call, DelegateCall, function selector, Calling Other Contract, Contract that Creates other Contracts.

Payable

声明payablefunctionaddress可以接收以太进入智能合约。
不要太纠结代码的transfer, call那些.

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

// Payable合约,允许接收和处理以太币
contract Payable {
    // Payable地址可以通过transfer或send发送以太币
    address payable public owner;

    // Payable构造函数,可以接收以太币
    constructor() payable {
        owner = payable(msg.sender);
    }

    // 函数用于向合约存入以太币。
    // 调用此函数时,请携带一些以太币。
    // 合约的余额将自动更新。
    function deposit() public payable {}

    // 调用此函数时,请携带一些以太币。
    // 由于此函数不可支付,将引发错误。
    function notPayable() public {}

    // 从合约中提取所有以太币的函数。
    function withdraw() public {
        // 获取存储在合约中的以太币数量
        uint amount = address(this).balance;

        // 将所有以太币发送到所有者地址
        (bool success, ) = owner.call{value: amount}("");
        require(success, "发送以太币失败");
    }

    // 从此合约向输入地址转移以太币的函数
    function transfer(address payable _to, uint _amount) public {
        // 请注意,“to”被声明为payable
        (bool success, ) = _to.call{value: _amount}("");
        require(success, "发送以太币失败");
    }
}

由于这个合约没做ether的存储, 所以transfer函数第二个参数是0才能执行.

Sending Ether (transfer, send, call)

发送

发送Ether到其它合约, 可以采取:

  • transfer (2300 gas, throws error)
  • send (2300 gas, returns bool)
  • call (forward all gas or set gas, returns bool)

接收

一个能接收Ether的合约必须要有以下其中一个函数:

  • receive() external payable
  • fallback() external payable
    如果msg.data为空则会调用receive(), 否则调用fallback()

细节

2019年12月之后使用call与重入保护(re-entrancy guard)相结合的方法。
通过在使用可重入保护修饰符调用其他契约之前更改所有状态来防止可重入
重入保护是通过以下措施:

  • 在调用其他合约之前更改所有状态
  • 使用重入保护修饰符(这里我猜测是之前提到的自定义一个locked变量的形式)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

contract ReceiveEther {
    /*
    哪个函数会被调用,fallback() 还是 receive()?

           发送以太币
               |
         msg.data 是否为空?
              / \
            是  否
            /     \
receive() 是否存在?  fallback()
         /   \
        是   否
        /      \
    receive()   fallback()
    */

    // 函数用于接收以太币。msg.data 必须为空
    receive() external payable {}

    // 当 msg.data 不为空时调用的回退函数
    fallback() external payable {}

    // 获取合约的余额
    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}

contract SendEther {
    // 通过 transfer 发送以太币的函数
    function sendViaTransfer(address payable _to) public payable {
        // 不再推荐使用此函数发送以太币。
        _to.transfer(msg.value);
    }

    // 通过 send 发送以太币的函数
    function sendViaSend(address payable _to) public payable {
        // send 返回一个布尔值,表示成功或失败。
        // 不推荐使用此函数发送以太币。
        bool sent = _to.send(msg.value);
        require(sent, "Failed to send Ether (sendViaSend func.)");
    }

    // ⭐通过 call 发送以太币的函数
    function sendViaCall(address payable _to) public payable {
        // call 返回一个布尔值,表示成功或失败。
        // 这是当前推荐的使用方法。
        (bool sent, bytes memory data) = _to.call{value: msg.value}("");
        require(sent, "Failed to send Ether (sendViaCall func.)");
    }
}

Fallback

fallback是一个特殊的函数,在以下情况下会被调用:

  • 当一个不存在的函数被调用
  • 以太币被直接发送到一个合约,但receive()不存在或msg.data非空.

当通过transfersend调用时,fallback有2300 gas限制。

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

contract Fallback {
    event Log(string func, uint gas);

    // 回退函数必须声明为 external。
    fallback() external payable {
        // send / transfer(将 2300 gas 转发到此回退函数)
        // call(转发所有的 gas)
        emit Log("fallback", gasleft());
    }

    // receive 是回退函数的一种变体,当 msg.data 为空时触发
    receive() external payable {
        emit Log("receive", gasleft());
    }

    // 辅助函数,用于检查合约的余额
    function getBalance() public view returns (uint) {
        return address(this).balance;
    }
}

contract SendToFallback {
    // 通过 transfer 发送以太币到回退函数的函数
    function transferToFallback(address payable _to) public payable {
        _to.transfer(msg.value);
    }

    // 通过 call 发送以太币到回退函数的函数
    function callFallback(address payable _to) public payable {
        (bool sent, ) = _to.call{value: msg.value}("");
        require(sent, "Failed to send Ether. ");
    }
}

fallback可以选择使用bytes作为输入和输出

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

// TestFallbackInputOutput -> FallbackInputOutput -> Counter
contract FallbackInputOutput {
    address immutable target;

    // 构造函数,接收目标合约地址作为参数
    constructor(address _target) {
        target = _target;
    }

    // 回退函数,接收输入数据并调用目标合约
    fallback(bytes calldata data) external payable returns (bytes memory) {
        // 使用call函数调用目标合约,转发以太币和数据
        (bool ok, bytes memory res) = target.call{value: msg.value}(data);
        require(ok, "failed to call.");
        return res;
    }
}

contract Counter {
    uint public count;

    // 获取计数值的函数
    function get() external view returns (uint) {
        return count;
    }

    // 增加计数值的函数
    function inc() external returns (uint) {
        count += 1;
        return count;
    }
}

contract TestFallbackInputOutput {
    event Log(bytes res);

    // 测试函数,调用传入的回退合约并记录返回结果
    function test(address _fallback, bytes calldata data) external {
        (bool ok, bytes memory res) = _fallback.call(data);
        require(ok, "failed to call");
        emit Log(res);
    }

    // 获取用于测试的数据,分别是Counter合约的get和inc函数的ABI编码
    function getTestData() external pure returns (bytes memory, bytes memory) {
        return (abi.encodeWithSignature("get()"), abi.encodeWithSignature("inc()"));
    }
}

要测试的时候,

  1. 部署Counter合约,
  2. Counter合约的地址作为FallbackInputOutput 合约的构造函数参数
  3. 部署FallbackInputOutput合约
  4. 部署TestFallbackInputOutput合约
  5. 执行getTestData()获取测试数据, 是由Countercount值的Bytes数据.
    测试结果

Call

Call是与其他合约交互的低级函数。
当你只是通过调用fallback函数发送ether时,这是推荐使用的方法。
然而,这不是调用现有函数的推荐方式
不建议使用低级调用的几个原因是:

  1. 不生成回显
  2. 忽略类型检查
  3. 忽略函数存在检查
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

// 注意不要把地址弄错, 不然看不到输出call foo的. 
contract Receiver {
    event Received(address caller, uint amount, string message);

    fallback() external payable {
        emit Received(msg.sender, msg.value, "Fallback was called");
    }

    function foo(string memory _message, uint _x) public payable returns (uint) {
        emit Received(msg.sender, msg.value, _message);

        return _x + 1;
    }
}

contract Caller {
    event Response(bool success, bytes data);

    // Let's imagine that contract Caller does not have the source code for the
    // contract Receiver, but we do know the address of contract Receiver and the function to call.
    function testCallFoo(address payable _addr) public payable {
        // You can send ether and specify a custom gas amount
        (bool success, bytes memory data) = _addr.call{value: msg.value, gas: 5000}(
            abi.encodeWithSignature("foo(string,uint256)", "call foo", 123)
        );

        emit Response(success, data);
    }

    // Calling a function that does not exist triggers the fallback function.
    function testCallDoesNotExist(address payable _addr) public payable {
        (bool success, bytes memory data) = _addr.call{value: msg.value}(
            abi.encodeWithSignature("doesNotExist()")
        );

        emit Response(success, data);
    }
}

Delegatecall

delegatecall是一个低级函数,类似于call
当合约A对合约B执行delegatcall时,B的代码将调用合约A的存储storage, msg.sendermsg.value作为参数.

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

// NOTE: Deploy this contract first
// 就结果来说A和B的deploy顺序没太大区别. 
contract B {
    // NOTE: storage layout must be the same as contract A
    uint public num;
    address public sender;
    uint public value;

    function setVars(uint _num) public payable {
        num = _num;
        sender = msg.sender;
        value = msg.value;
    }
}

contract A {
    uint public num;
    address public sender;
    uint public value;

    function setVars(address _contract, uint _num) public payable {
        // A's storage is set, B is not modified.
        (bool success, bytes memory data) = _contract.delegatecall(
            abi.encodeWithSignature("setVars(uint256)", _num)
        );
    }
}

意思是说A调用了B的函数setVars用来设置自己的状态变量state variable.

Function Selector

当调用函数时,calldata的前4个字节指定要调用哪个函数。
这4个字节称为函数选择器function selector.
以下面的代码为例。它使用call在地址addr的合约上执行transfer
addr.call(abi.encodeWithSignature("transfer(address,uint256)", 0xSomeAddress, 123))
abi.encodeWithSignature(....)返回的前4个字节是函数选择器。
如果在代码中预先计算并内联函数选择器,也许可以节省少量的gas?
下面是函数选择器的计算方式。

视频案例: https://www.youtube.com/watch?v=Mn4e4w8h6n8

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

contract FunctionSelector {
    /*
    "transfer(address,uint256)"
    0xa9059cbb
    "transferFrom(address,address,uint256)"
    0x23b872dd
    */
    function getSelector(string calldata _func) external pure returns (bytes4) {
        return bytes4(keccak256(bytes(_func)));
    }
}

contract Receiver {
    event Log(bytes data);
    function transfer(address _to, uint amount) external {
        emit Log(msg.data);
        // data: 
        // 前4个字节: 0xa9059cbb
        // 地址: 0000000000000000000000005b38da6a701c568545dcfcb03fcb875f56beddc4
        // 数据(我输入的是11, 所以最后是b): 
        // 000000000000000000000000000000000000000000000000000000000000000b
    }
}

"transfer(address,uint256)"必须完整, 多一个空格都不行, 不然前4个字节结果就不是0xa9059cbb. 这是给函数编码(encode)后的形式.

Calling Other Contract

合约可以通过两种方式调用其他合约。
最简单的方法是直接调用它,就像A.foo(x, y, z)一样。
另一种调用其他契约的方法是使用低级调用(call)。不推荐使用该方法。

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

contract Callee{
    uint public x;
    uint public value;
    function setX(uint _x) public returns(uint){
        x = _x;
        return x;
    }
    function getX() external view returns(uint){
        return x;
    }
    function setXandSendEther(uint _x) public payable returns (uint, uint){
        x = _x;
        value = msg.value;
        return (x,value);
    }
}

contract Caller{
    function setX(Callee _callee, uint _x) public {
        uint x = _callee.setX(_x);
    }
    function getX(address _callee, Callee _c) public view returns(uint x){
        uint i = Callee(_callee).getX();
        uint j = _c.getX();
        return i == j ? 1 : 0;
    }
    function setXFromAddress(address _addr, uint _x) public {
        Callee callee = Callee(_addr);
        callee.setX(_x);
    }
    function setXandSendEther(Callee _callee, uint _x) public payable {
        (uint x, uint value) = _callee.setXandSendEther{value: msg.value}(_x);
    }
}

Contract that Creates other Contracts

合约可以通过new关键字由其他合约创建。从0.8.0开始,new关键字通过指定salt选项来支持create2特性。

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

contract Car {
    address public owner;
    string public model;
    address public carAddr;

    constructor(address _owner, string memory _model) payable {
        owner = _owner;
        model = _model;
        carAddr = address(this);
    }
}

contract CarFactory {
    Car[] public cars;

    function create(address _owner, string memory _model) public {
        Car car = new Car(_owner, _model);
        cars.push(car);
    }

    function createAndSendEther(address _owner, string memory _model) public payable {
        Car car = (new Car){value: msg.value}(_owner, _model);
        cars.push(car);
    }

    function create2(address _owner, string memory _model, bytes32 _salt) public {
        Car car = (new Car){salt: _salt}(_owner, _model);
        cars.push(car);
    }

    function create2AndSendEther(
        address _owner,
        string memory _model,
        bytes32 _salt
    ) public payable {
        Car car = (new Car){value: msg.value, salt: _salt}(_owner, _model);
        cars.push(car);
    }

    function getCar(
        uint _index
    )
        public
        view
        returns (address owner, string memory model, address carAddr, uint balance)
    {
        Car car = cars[_index];

        return (car.owner(), car.model(), car.carAddr(), address(car).balance);
    }
}

标签:function,学习,Solidity,value,语法,payable,uint,address,public
From: https://www.cnblogs.com/joey-redfield/p/17899736.html

相关文章

  • 算法学习Day1,二分查找,移除元素
    Day1二分查找,移除元素ByHQWQF2023/12/13笔记704.二分查找给定一个n个元素有序的(升序)整型数组nums和一个目标值target,写一个函数搜索nums中的target,如果目标值存在返回下标,否则返回-1。解法:使用二分查找来在一个有序的数组中找到指定元素的下标。根据数据边界......
  • Cesium学习笔记11——坐标量测
     代码:1varannotations;2functionaxToolCoordinate(layer){3varhandler=newCesium.ScreenSpaceEventHandler(viewer.canvas);4annotations=viewer.scene.primitives.add(newCesium.LabelCollection());5handler.setInputAction(function(......
  • 商用车重卡市场学习
    漫谈中国特色的重卡金融产品 18年中国的汽车消费信贷起步较晚,1998年10月,中国人民银行正式发布《汽车金融管理条例》,四大商业银行被授权经营汽车贷款业务。当时保险公司为业务拓展及市场份额,也推出了汽车消费贷款保证保险。保险公司的参与增加了金融机构的信心,同时也降低了相关业......
  • Solidity智能合约例子:存证合约
    一、合约编写感谢b站上的UP老哥冲少,这里参考了他的视频。一共是4个合约,1个是权限控制,1个存证、1个存证申请(是否可以存证有投票机制)、还一个入口合约。Authentication.sol//SPDX-License-Identifier:UNLICENSEDpragmasolidity^0.8.9;contractAuthentication{addre......
  • pytest框架学习-pytest_addoption钩子函数
    适用场景:一套自动化代码,多套环境。pytest_addoption允许用户自定义注册一个命令行参数,方便用户通过命令行参数的形式给pytest传递不同的参数进行不同测试场景的切换。pytest_addoption钩子函数一般和内置fixturepytestconfig配合使用,pytest_addoption注册自定义的命令......
  • 单片机学习-电路分析
    目录单片机学习-电路分析为什么要学习电路分析电路分析的基础知识如何应用这些知识看懂51和STM32的开发板原理图?深入电路分析基础看懂51和STM32开发板原理图的进阶技巧总结与展望常见电路图符号大全电路图符号大全基本电路符号传输路径符号集成电路组件符号限定符号开关和继电器符......
  • 多层语法糖嵌套
    装饰器多层语法糖嵌套小练习:##先验证登录##再验证输入的金额---符合数字/余额充足###取款函数里面#defget_balance():##校验登录##校验金额符合数字/余额充足---把金额通过装饰器返回来##拿着你的金额进行提款use_data={'usern......
  • java学习01-项目,模块,包,类的关系
    一.引言在Java编程语言中,项目、模块、包和类是组织代码的不同层次,它们之间的关系构成了Java程序的框架和结构。下面将详细解释这些概念以及它们之间的关系。二.定义项目(Project):项目是最大的组织单位,通常代表一个完整的软件应用或一组相关的应用。一个项目可以包含多个模块,这些......
  • 单片机学习-电气基础
    目录单片机学习-电气基础电压与电流交流电与直流电直流电交流电电阻闭合电路欧姆定律电流的功和功率导体的电容磁学半导体N型半导体和P型半导体PN结半导体二极管半导体三极管(晶体管)门电路和组合逻辑电路门电路的基本概念二极管“与”门电路二极管“或”门电路晶体管“非”门电......
  • Rong晔大佬教程学习(1):背景与项目设计目标
    riscv实际上是一种ISA的指令集,而处理器的设计的基本结构是不变的(如下所示),其区别在于所选用的指令集的类型,一般有ARM、RISCV、MIPS等,采用了不同的引擎,那么车的外观、系统等也会随之发生变化。采用RISCV,是因为它简洁、开源、明了,确定处理器设计的指令集后,我们还要对其进行......