再谈solidity中的抽象合约

基本概念:

Solidity 中,抽象合约是一种合约,它至少包含一个没有实现主体的函数。这些函数通常被标记为virtual(如果它们打算被重写)和abstract。抽象合约不能被直接实例化,它主要是作为其他合约的基类,用于定义接口和公共的函数签名,以规范继承它的合约的行为。

一个基本的抽象合约示例:

csharp 复制代码
pragma solidity ^0.8.0;

abstract contract AbstractContract {
    function someFunction() public virtual view returns (uint) external;
}

在这个例子中,someFunction是一个抽象函数。它有返回类型uint,访问修饰符是public,函数类型是view(表示这个函数不会修改合约的状态)和external(意味着它只能从合约外部调用)。但是没有函数体,具体的实现留给继承这个抽象合约的具体合约。

抽象合约的目的和用途:

接口规范 :抽象合约用于定义一组函数签名,这些签名构成了一种接口。继承抽象合约的子合约必须实现这些抽象函数,从而保证了在基于接口开发时,不同合约之间函数调用的一致性。例如,在开发一个去中心化金融(DeFi)应用中,可能有一个抽象合约定义了通用的tokenTransfer函数签名,不同的 token 合约继承这个抽象合约并按照自己的规则实现tokenTransfer,但它们都遵循相同的接口规范,方便其他合约与之交互。

代码复用和继承结构 :它为合约的继承体系提供了一个基础。可以把一些通用的状态变量定义和部分函数实现放在抽象合约中,然后让多个具体合约继承它。这样,既可以复用代码,又可以通过抽象函数来强制要求子类实现某些特定的功能。比如,一个抽象的Asset合约可以定义资产的基本属性(如assetNameassetSymbol),以及抽象的transferbalanceOf函数。然后,TokenAssetNFTAsset等具体资产合约可以继承Asset合约,根据自身特点实现transferbalanceOf函数。

与接口的区别:

虽然抽象合约和接口都用于定义函数签名,但接口更加严格。接口中的所有函数都自动是externalabstract的,不能有函数体,也不能包含状态变量。而抽象合约可以有状态变量和带有函数体的函数,它只是部分函数可以是抽象的。例如,一个接口定义可能如下:

php 复制代码
pragma solidity ^0.8.0;

interface MyInterface {
    function anotherFunction() external returns (bool);
}

抽象合约中的状态变量:

抽象合约可以定义状态变量,这些状态变量会被继承它的子合约继承。例如,抽象合约定义了一个uint类型的状态变量totalSupply,如下所示:

csharp 复制代码
pragma solidity ^0.8.0;

abstract contract AbstractToken {
    uint public totalSupply;
}

这个totalSupply变量可以在抽象合约中进行初始化,也可以在子合约的构造函数中初始化。如果在抽象合约中初始化,那么所有继承该抽象合约的子合约都会继承这个初始值。

子合约的存储布局:

继承状态变量的存储位置 :当子合约继承抽象合约的状态变量时,这些状态变量会按照定义的顺序在存储中分配位置。在以太坊虚拟机(EVM)中,存储是一个键 - 值对的存储区域,状态变量存储在其中。继承的状态变量会占用连续的存储槽(storage slot)。例如,如果抽象合约中有一个uint(占用 32 字节)类型的状态变量var1,子合约继承了这个变量,var1会占用一个存储槽。如果子合约又定义了自己的uint类型状态变量var2var2会占用下一个存储槽。

变量重名情况:如果子合约定义了与抽象合约中同名的状态变量,那么子合约中的变量会覆盖抽象合约中的变量。但是这种做法可能会导致一些混淆,并且在大多数情况下应该避免,除非有明确的目的。例如:

csharp 复制代码
pragma solidity ^0.8.0;

abstract contract AbstractContract {
    uint public variable;
}
contract ChildContract is AbstractContract {
    uint public variable; // 覆盖了抽象合约中的variable变量
}

状态变量的访问和修改:

访问规则 :子合约可以直接访问从抽象合约继承的状态变量。这些状态变量的访问修饰符(如publicprivateinternal)在继承过程中遵循 Solidity 的访问规则。如果一个状态变量在抽象合约中被定义为public,子合约和外部合约(在允许的情况下)都可以访问它。例如,对于前面定义的AbstractToken合约中的totalSupply变量,如果子合约MyToken继承了AbstractTokenMyToken合约内部的函数可以直接访问totalSupply,并且外部合约也可以通过MyToken合约实例来访问totalSupply

修改规则 :子合约可以修改从抽象合约继承的状态变量。当子合约修改这些变量时,存储中的值会相应地更新。例如,子合约可以通过一个函数来增加totalSupply的值,如下所示:

csharp 复制代码
pragma solidity ^0.8.0;

abstract contract AbstractToken {
    uint public totalSupply;
}
contract MyToken is AbstractToken {
    function increaseSupply(uint amount) public {
        totalSupply += amount;
    }
}

在这个例子中,MyToken合约中的increaseSupply函数可以修改从抽象合约AbstractToken继承的totalSupply状态变量。这种修改会直接影响存储中的值,并且在整个区块链网络中保持更新后的状态。

存储布局对 Gas 成本的影响:

状态变量的存储布局会影响合约执行的 Gas 成本。在 EVM 中,读取和写入存储是相对昂贵的操作。如果状态变量的布局不合理,可能会导致更高的 Gas 消耗。例如,如果频繁地访问存储中不连续的状态变量,会比访问连续的状态变量消耗更多的 Gas。当子合约继承抽象合约的状态变量时,合理地设计抽象合约中的状态变量布局可以帮助减少子合约的 Gas 成本。比如,将经常一起使用的状态变量在抽象合约中连续定义,这样在子合约继承后,它们在存储中的位置也相对连续,有利于降低 Gas 成本。

相关推荐
木西14 天前
实现一个简洁版的NFT交易所
web3·智能合约·solidity
用户74921347159721 天前
solidity(基础特性)—学习总结
solidity
我是前端小学生2 个月前
单个solidity合约的文件结构
solidity
天涯学馆2 个月前
从零到英雄:Solidity 智能合约开发全攻略
后端·智能合约·solidity
MavenTalk2 个月前
Move开发语言在区块链的开发与应用
开发语言·python·rust·区块链·solidity·move
jc0803kevin2 个月前
solidity call使用
区块链·solidity
jc0803kevin2 个月前
solidity selfdestruct合约销毁
区块链·solidity
jc0803kevin2 个月前
solidity的struct对象,web3j java解析输出参数
java·web3·solidity
Keegan小钢3 个月前
智能合约开发工具Remix
web3·智能合约·solidity