全面掌握Solidity智能合约开发

2024年09月25日更新 796 人订阅
原价: ¥ 46 限时优惠
专栏简介 跟我学 Solidity :开发环境 跟我学 Solidity:关于变量 跟我学 Solidity : 变量的存储 跟我学 Solidity :引用变量 跟我学 Solidity :函数 跟我学 Solidity :合约的创建和继承 跟我学 Solidity :工厂模式 用Web3.js构建第一个Dapp 跟我学Solidity:事件 Solidity 中 immutable (不可变量)与constant(常量) [译] Solidity 0.6.x更新:继承 解析 Solidity 0.6 新引入的 try/catch 特性 探究新的 Solidity 0.8 版本 探索以太坊合约委托调用(DelegateCall) 停止使用Solidity的transfer() 使用工厂提高智能合约安全性 Solidity 怎样写出最节省Gas的智能合约[译] Solidity 优化 - 编写 O(1) 复杂度的可迭代映射 Solidity 优化 - 控制 gas 成本 Solidity 优化 - 减少智能合约的 gas 消耗的8种方法 Solidity 优化 - 如何维护排序列表 Solidity 优化:打包变量优化 gas 使用 Solidity 瞬态存储操作码 在 Solidity中使用值数组以降低 gas 消耗 Gas 优化:Solidity 中的使用动态值数组 计算Solidity 函数的Gas 消耗 Solidity 技巧:如何减少字节码大小及节省 gas 一些简单的 Gas 优化基础 "Stack Too Deep(堆栈太深)" 解决方案 智能合约Gas 优化的几个技术 合约实践:避免区块Gas限制导致问题 如何缩减合约以规避合约大小限制 Solidity 类特性 无需gas代币和ERC20-Permit还任重而道远 智能合约实现白名单的3个机制 Solidity智能合约安全:防止重入攻击的4种方法 Solidity 十大常见安全问题 [译]更好Solidity合约调试工具: console.log 智能合约开发的最佳实践 - 强烈推荐 全面理解智能合约升级 Solidity可升级代理模式: 透明代理与UUPS代理 使用OpenZeppelin编写可升级的智能合约 实战:调整NFT智能合约,减少70%的铸币Gas成本 Solidity 优化 - 隐藏的 Gas 成本 Gas 技巧:Solidity 中利用位图大幅节省Gas费 Solidity Gas 优化 - 理解不同变量 Gas 差异 关于Solidity 事件,我希望早一点了解到这些 Solidity 编码规范推荐标准 深入了解 Solidity bytes OpenZeppelin Contracts 5.0 版本发布 Solidity Gas优化:高效的智能合约策略 智能合约安全的新最低测试标准:Fuzz / Invariant Test 智能合约的白名单技术 模糊测试利器 - Echidna 简介 智能合约设计模式:代理 离线授权 NFT EIP-4494:ERC721 -Permit

跟我学 Solidity :合约的创建和继承

如何在合约里创建合约

欢迎阅读“跟我学 Solidity ”系列中的又一篇文章。在上一篇文章,我们看到了如何使用函数,并运用了到目前为止所学到的一切来构建一个多签名钱包。

在本文中,我们将看到如何从一个合约中创建另一个合约,以及如何定义抽象合约和接口。

合约创建

可以通过以太坊交易或在Solidity合约中使用new关键字创建合约,new关键字将部署该合约的新实例并返回合约地址。

通过Solidity文档中给出的示例,让我们仔细看看它是如何工作的。我将name变量设为public,以便我们可以读取到它的值,并且还会和createToken函数的返回值一起创建一个事件(关于事件,也会有其他的文章介绍):

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.4.22 <0.8.0;

contract OwnedToken {
    TokenCreator creator;
    address owner;
    bytes32 public name;
    constructor(bytes32 _name) {
        owner = msg.sender;
        creator = TokenCreator(msg.sender);
        name = _name;
    }

    function changeName(bytes32 newName) public {
        if (msg.sender == address(creator))
            name = newName;
    }

    function transfer(address newOwner) public {
        if (msg.sender != owner) return;

        if (creator.isTokenTransferOK(owner, newOwner))
            owner = newOwner;
    }
}

contract TokenCreator {
    event TokenCreated(bytes32 name, address tokenAddress);

    function createToken(bytes32 name)
        public
        returns (OwnedToken tokenAddress)
    {
        tokenAddress =  new OwnedToken(name);
        emit TokenCreated(name, address(tokenAddress));
    }

    function changeName(OwnedToken tokenAddress, bytes32 name) public {

        tokenAddress.changeName(name);
    }

    function isTokenTransferOK(address currentOwner, address newOwner)
        public
        pure
        returns (bool ok)
    {

        return keccak256(abi.encodePacked(currentOwner, newOwner))[0] == 0x7f;
    }
}

代码

这次,我们使用Tuffle框架来辅助开发,可以参考快速入门指南进行项目设置。

首先,我们将创建一个新项目并通过执行以下命令对其进行初始化:

> mkdir token
> cd token
> truffle init

打开项目,并更新truffle-config.js文件,设置部署合约的节点 RPC 的IP和端口(这里使用Ganache运行的本地网络)以及使用的Solidity编译器的版本。

现在,我们可以在contracts文件夹中创建合约文件TokenCreator.sol,复制前面的代码并粘贴.在migrations文件夹中创建一个迁移文件,以部署TokenCreator合约。将其命名为2_deploy_token.js,然后复制并粘贴以下代码。

const TokenCreator = artifacts.require("TokenCreator");module.exports = function (deployer) {
     deployer.deploy(TokenCreator);
};

返回命令行终端,输入truffle console以启动Truffle控制台,你可以在控制台中编译和部署合约:

Typing ‘truffle console’ in the terminal to launch the Truffle console.

使用compile 命令编译合约,使用 migrate 命令部署合约。

我们现在要做的是检索已部署的TokenCreator的实例。然后,进行两次调用createToken函数,并保存每个新创建合约的地址。

By typing ‘tokenCreator.address’ we can double-check that the same address was displayed when we deployed the contract.

如果使用的是Ganache,你会看到两个代表合约调用的交易被添加到交易列表中,其中数据字段设置为四个字节的函数选择器和传递的参数。如果你想知道真正发生了什么以及如何创建这些合约,请订阅本专栏

众所周知,合约只是另一种帐户,因此,当我们调用createToken函数时,实际上发生的是状态数据库更新为包括新创建的帐户,并且账户的四个变量(noncebalancestorage_rootcode_hash)已正确初始化(每个帐户都会包含这四个变量)。

如果现在回到Truffle控制台,则可以检查每个交易的日志以获取每个合约的地址,然后可以调用name 函数来验证它们确实是两个单独的合约实例。

The logs of each transaction that you can examine to get the address of each contract.

The user has identified the names ‘kitty’ and ‘sweet’ by looking at the logs.

关于web3.js的更多信息可以在这里找到。

构造函数声明

合约的构造函数在创建合约时被调用,并且不会与其余的合约代码一起存储在区块链上。 构造函数是可选的。只允许一个构造函数,这意味着构造函数不支持重载。

使用关键字constructor声明构造函数:

contract A {
     uint a;
     bool b;
     constructor(uint _a, bool _b){
        a = _a;
        b = _b;
   }
   ...
}

抽象合约

如果合约中的至少一个函数没有实现,则合约需要标记为abstract。即使实现了所有函数,合约也可能被标记为abstract

抽象合约通过使用关键字abstract来完成,未实现的函数应具有关键字virtual以表示允许多态

abstract contract A {
    function f() public pure virtual;
}

抽象合约是直接实例化(部署),即使它实现了所有函数。它们可以用作定义特定行为的基础合约(就像面向对象里面的基类)用来给其他合约继承。实现函数应用override关键字修饰。

abstract contract A {
    function f() public pure virtual;
}

abstract contract B is A {
    function f() public pure override {
       //function body
    }
}

如果派生合约未实现所有未实现的函数,则也需要将其标记为abstract

接口

接口类似于抽象合约,但是不能实现任何函数。还有其他限制:

  • 它们不能从其他合约继承,但是可以从其他接口继承
  • 所有声明的函数必须是外部的
  • 他们不能声明构造函数
  • 他们不能声明状态变量

使用关键字interface声明接口。

interface A {
    function f() external pure;
} 

接口中声明的所有函数都是隐式的virtual

结论

本文就是这样。本文参考文档,在接下来的文章中,我们将深入研究智能合约开发。欢迎关注。


本翻译由 Cell Network 赞助支持。

点赞 9
收藏 2
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

7 条评论

请先 登录 后评论