在手把手教你实现Bank智能合约这篇文章中,我们认真拆解了需求,罗列了相关的知识点,已经实现了基于权限控制的存款和取款逻辑。这篇文章,我们会提升难度,不仅仅要实现功能,还要思考怎么合理的去设计一个合约。
在手把手教你实现Bank智能合约这篇文章中,我们认真拆解了需求,罗列了相关的知识点,已经实现了基于权限控制的存款和取款逻辑。这篇文章,我们会提升难度,不仅仅要实现功能,还要思考怎么合理的去设计一个合约。
1、新的BigBank
合约要继承Bank
合约;
2、BigBank
合约支持转移自己的管理员权限到其他的指定账户;
3、需要编写一个Ownable
合约,将BigBank
合约的管理员权限转移给Ownable
合约,转移之后,只有Ownable
合约才可以调用BigBank
合约的 withdraw()
方法。
4、新的管理员调用withdraw()
方法,可以成功从BigBank
合约中把钱取走。
5、需要实现对存款前三名用户的排序。
题目的要求中涉及到了继承的知识点,我在Solidity中的继承这篇文章中介绍过,Solidity是一种面向对象的编程语言,它支持合约之间的继承。继承允许一个合约获取另一个合约的所有非私有属性和函数,这样就可以重复使用代码,降低重复工作量。
接口可以理解为一种约束,我们可以通过接口定义标准,通过接口定义一组函数签名,为智能合约提供标准化的接口。这对于创建可互操作的合约系统非常有用。
从抽象角度理解,接口允许你抽象出合约的功能,而不需要关心具体实现。这有助于提高代码的可读性和可维护性。
从多态的角度理解,通过接口,允许不同的合约实现相同的接口,但有不同的具体实现。
在类型检查层面,编译器可以使用接口进行类型检查,确保合约正确实现了所有必要的函数,实现过接口的朋友们都理解,如果继承了一个接口却没有实现它的全部函数,编译器就会报错,大大提高了代码的规范和安全性。
回顾了上面两个基础的概念,我们规划下整体合约的设计:
1、我们需要在上层抽象出一个接口,这个接口就叫做IBank
,它的作用是抽象出Bank
合约的能力,包括存款、取款、事件、还有一些可见的状态变量的定义。
2、我们在手把手教你实现Bank智能合约这篇文章中实现的Bank
合约是需要继承IBank
接口的,只需要稍加改造即可实现(包含对于函数的重写)
3、BigBank
合约是我们要新创建的合约,这个合约需要继承Bank
合约,从这里就可以看出继承的好处了,根据题目要求,我们仅仅需要重写deposit
方法,添加一些自己的方法即可,一些其他的公用部分都可以通过继承获得。
4、Ownable
这个合约我们也需要新创建,BigBank
合约可以将管理员权限交给它,用于只能通过Ownable
合约去调用withdraw
函数。
整体的调用关系可以如下图所示:
IBank
这个接口实现很简单,我们只需要将Bank
合约的相关功能抽象出来即可:
// Define IBank interface
interface IBank {
// Event definitions
event Withdrawal(address indexed to, uint256 amount);
event Deposit(address indexed from, uint256 amount);
// Function definitions to be implemented
// Getter function for public state variable
function owner() external view returns (address);
// Deposit function
function deposit() external payable;
// Withdraw function
function withdraw(uint256 amount) external;
// Get balance for a specific address
function getBalance(address addr) external view returns (uint256);
// Get top depositors
function getTopDepositors() external view returns (address[] memory);
}
这个接口提供了一个标准化的结构。它定义了基本的存款、取款功能,以及一些辅助功能如查询余额和获取top存款人。通过使用这个接口,可以确保任何实现它的合约都会包含这些基本功能,从而提高代码的一致性和可互操作性。
接下来,我们需要对Bank
合约做一些简单的改造,添加一些重写的关键字,这部分并不复杂,我们直接贴代码:
// OriginalBank contract implementing IBank interface
contract OriginalBank is IBank {
address public owner;
mapping(address => uint256) private balances;
address[] public topDepositors;
// Constructor to set the contract owner
constructor() {
owner = msg.sender;
}
// Modifier to restrict function access to owner only
modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call this function");
_;
}
// Fallback function to handle direct ETH transfers
receive() external payable {
deposit();
}
// Deposit function to add funds to the contract
function deposit() public payable virtual override {
balances[msg.sender] += msg.value;
updateTopDepositors(msg.sender);
emit Deposit(msg.sender, msg.value);
}
// Withdraw function to transfer funds to the owner
function withdraw(uint256 amount) public override onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "Contract balance is zero");
require(amount <= balance, "Insufficient contract balance");
payable(owner).transfer(amount);
emit Withdrawal(owner, amount);
}
// Internal function to update the list of top depositors
function updateTopDepositors(address depositor) internal {
bool exists = false;
for (uint256 i = 0; i < topDepositors.length; i++) {
if (topDepositors[i] == depositor) {
exists = true;
break;
}
}
if (!exists) {
topDepositors.push(depositor);
}
// Sort depositors based on their balance
for (uint256 i = 0; i < topDepositors.length; i++) {
for (uint256 j = i + 1; j < topDepositors.length; j++) {
if (balances[topDepositors[i]] < balances[topDepositors[j]]) {
address temp = topDepositors[i];
topDepositors[i] = topDepositors[j];
topDepositors[j] = temp;
}
}
}
// Keep only the top 3 depositors
if (topDepositors.length > 3) {
topDepositors.pop();
}
}
// Get balance for a specific address
function getBalance(address addr) public view override returns (uint256) {
return balances[addr];
}
// Get the list of top depositors
function getTopDepositors()
public
view
override
returns (address[] memory)
{
return topDepositors;
}
}
为了便于理解,我将Bank
合约换了一个名字OriginalBank
,这里稍微说明一下,从实现上可以看到,我们在实现相关接口函数定义的时候,需要使用override
关键字,这个知识点,我在Solidity中的继承也做了说明。
题目中要求,我们需要对用户的存款金额做个限制,在OriginalBank
合约中,我们并没有做相关的功能,所以在BigBank
合约中我们需要重写这部分的能力,还是使用modifier这个修饰器。
为了可以转移自己的所有权,我们还需要提供一个函数,将权限向指定的地址移交。
// BigBank contract inheriting from OriginalBank
contract BigBank is OriginalBank {
// Define minimum deposit amount
uint256 private constant MIN_DEPOSIT = 1_000_000_000_000_000; // 0.001 ETH in wei
// Event for ownership transfer
event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);
// Modifier to check if deposit amount is greater than minimum required
modifier minDepositRequired() {
require(msg.value > MIN_DEPOSIT, "Deposit must be greater 0.001 ether");
_;
}
// Override deposit function with minDepositRequired modifier
function deposit() public payable override minDepositRequired {
super.deposit();
}
// Function to transfer ownership to a new address
function transferOwnership(address newOwner) public onlyOwner {
require(newOwner != address(0), "New owner cannot be the zero address");
require(newOwner != owner, "New owner cannot be the current owner");
address oldOwner = owner;
owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
在deposit
函数中,我们用到了super
关键字,在Solidity中用于调用父合约的函数。当一个合约继承另一个合约并重写(override)了某个函数时,使用super
可以调用父合约中的原始实现。这在你想扩展父合约功能而不是完全替换它时特别有用。
Ownable
合约Ownable
合约中会提供一个withdraw
函数,这个函数是对BigBank
函数中withdraw
函数的包装,换句话说,我们需要在内部实现具体的合约地址调用其方法,这里还需要实现一个IBigBank
接口;
// Define IBigBank interface
interface IBigBank {
function withdraw(uint256 amount) external;
}
具体的,Ownable
合约可以这样写:
// Ownable contract to manage ownership and interact with BigBank
contract Ownable {
address public owner; // Owner's address
IBigBank public bigBank;
// Constructor to set the initial owner
constructor() {
owner = msg.sender;
}
// Modifier to restrict function access to owner only
modifier onlyOwner() {
require(msg.sender == owner, "Only the owner can call this function.");
_;
}
// Function to set the BigBank contract address
function setBigBankAddress(address _bigBankAddress) public onlyOwner {
bigBank = IBigBank(_bigBankAddress);
}
// Function to withdraw from BigBank
function withdraw(uint256 amount) public onlyOwner {
require(address(bigBank) != address(0), "BigBank address not set");
bigBank.withdraw(amount);
}
// Fallback function to receive ETH
receive() external payable {}
}
1、部署顺序,先部署BigBank
合约, 验证取款的金额必须大于0.001 ether;
2、验证其他三个账号向其中转账,金额都不同,最后只有部署合约的账号可以取款;
3、部署Ownable合约,得到一个地址,将BigBank合约的所有权转移给它,此时,BigBank 就无法调用提款方法了。因为合约所有权已经转移;
4、切换为部署Ownable合约的账号,调用取款方法,可以成功取款不会报错。
interface
关键字定义接口event
)和函数签名OriginalBank
实现 IBank
接口BigBank
继承自 OriginalBank
override
关键字重写父合约的函数public
、private
关键字控制变量可见性mapping
用于存储键值对(地址到余额的映射)constructor
关键字定义modifier
关键字定义onlyOwner
)public
、external
、internal
、private
关键字view
关键字用于不修改状态的函数payable
关键字用于可接收以太币的函数event
关键字定义require
语句进行条件检查和错误处理receive()
函数用于接收以太币constant
关键字定义不可变的值address
类型表示以太坊地址payable(address).transfer()
用于转账IBigBank
接口定义了 BigBank
合约的部分功能Ownable
合约通过接口调用 BigBank
的 withdraw
函数setBigBankAddress
)实现一定程度的可升级性这个合约展示了 Solidity 编程中的多个高级概念,包括接口、继承、访问控制、事件处理和合约间交互等。它提供了一个基本的银行系统实现,同时考虑了安全性和可扩展性。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!