Solidity 102
函数重载
和C++中的一样:参数返回值不同,函数名相同。
库合约
库合约是一种特殊的合约,为了提升Solidity代码的复用性和减少gas而存在。库合约是一系列的函数合集。
与普通合约的区别
- 不能存在状态变量。
- 不能够继承或被继承。
- 不能接收以太币。
- 不可以被销毁。
需要注意的是,库合约中的函数可见性如果被设置为public或者external,则在调用函数时会触发一次delegatecall。而如果被设置为internal,则不会引起。对于设置为private可见性的函数来说,其仅能在库合约中可见,在其他合约中不可用。
使用
// 方式1:
// 利用using for指令
using Strings for uint256;
function getString1(uint256 _number) public pure returns(string memory){
// 库合约中的函数会自动添加为uint256型变量的成员
return _number.toHexString();
}
// 方式2:
// 直接通过库合约名调用
function getString2(uint256 _number) public pure returns(string memory){
return Strings.toHexString(_number);
}Import
目录结构
文件结构
├── Import.sol
└── Yeye.sol
// 通过文件相对位置import
import './Yeye.sol';直接饮用网上的合约的全局地址
// 通过网址引用
import 'https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Address.sol';通过npm的目录导入,例子:
import '@openzeppelin/contracts/access/Ownable.sol';通过指定全局符号导入合约特定的全局符号,例子:
import {Yeye} from './Yeye.sol';接收ETH
Solidity 有两种特殊的回调函数,receive() 和 fallback(), 在以下情况被使用:
- 接收ETH
- 处理合约中不存在的函数调用(代理合约proxy contract)
这里主要讲接收ETH的情况
receive()
合约收到ETH转账的时候被调用,一个合约最多有一个receive函数,声明方式不需要function,不需要参数和返回值,且必须包含external payable
receive() external payable { ... }fallback
fallback()函数会在调用合约不存在的函数时被触发。可用于接收ETH,也可以用于代理合约proxy contract。
fallback() external payable { ... }两者区别
两者触发的规则如下:
触发fallback() 还是 receive()?
接收ETH
|
msg.data是空?
/ \
是 否
/ \
receive()存在? fallback()
/ \
是 否
/ \
receive() fallback()发送ETH
有三种方法向其他合约发送ETH,他们是:transfer(),send()和call(),其中call()是被鼓励的用法。
先部署一个接收ETH的合约:
contract ReceiveETH {
// 收到eth事件,记录amount和gas
event Log(uint amount, uint gas);
// receive方法,接收eth时被触发
receive() external payable{
emit Log(msg.value, gasleft());
}
// 返回合约ETH余额
function getBalance() view public returns(uint) {
return address(this).balance;
}
}发送 ETH 合约
首先,先在发送ETH合约SendETH中实现payable的构造函数和receive(),让我们能够在部署时和部署后向合约转账
contract SendETH {
// 构造函数,payable使得部署的时候可以转eth进去
constructor() payable{}
// receive方法,接收eth时被触发
receive() external payable{}
}transfer
用法:
接收方地址.transfer(发送ETH数额)-
transfer()的gas限制是2300,足够用于转账,但对方合约的fallback()或receive()函数不能实现太复杂的逻辑。 -
transfer()如果转账失败,会自动revert(回滚交易)。
// 用transfer()发送ETH
function transferETH(address payable _to, uint256 amount) external payable{
_to.transfer(amount);
}部署两个合约之后点击transferETH输入ReceiveETH合约的地址和数额,就可以看到发送成功
send
用法:
接收方地址.send(发送ETH数额)-
send()的gas限制是2300,足够用于转账,但对方合约的fallback()或receive()函数不能实现太复杂的逻辑。 -
send()如果转账失败,不会revert(回滚交易)。 -
send()的返回值是bool,代表着转账成功或失败,需要额外代码处理一下
error SendFailed(); // 用send发送ETH失败error
// send()发送ETH
function sendETH(address payable _to, uint256 amount) external payable{
// 处理下send的返回值,如果失败,revert交易并发送error
bool success = _to.send(amount);
if(!success){
revert SendFailed();
}
}call
用法是:
接收方地址.call{value: 发送ETH数额}("")call()没有gas限制,可以支持对方合约fallback()或receive()函数实现复杂逻辑。call()如果转账失败,不会revert。call()的返回值是(bool, bytes),其中bool代表着转账成功或失败,需要额外代码处理一下。
error CallFailed(); // 用call发送ETH失败error
// call()发送ETH
function callETH(address payable _to, uint256 amount) external payable{
// 处理下call的返回值,如果失败,revert交易并发送error
(bool success,) = _to.call{value: amount}("");
if(!success){
revert CallFailed();
}
}调用其他合约
在Solidity中,一个合约可以调用另一个合约的函数,这在构建复杂的DApps时非常有用。
先写一个合约,用于被其他合约调用
contract OtherContract {
uint256 private _x = 0; // 状态变量_x
// 收到eth的事件,记录amount和gas
event Log(uint amount, uint gas);
// 返回合约ETH余额
function getBalance() view public returns(uint) {
return address(this).balance;
}
// 可以调整状态变量_x的函数,并且可以往合约转ETH (payable)
function setX(uint256 x) external payable{
_x = x;
// 如果转入ETH,则释放Log事件
if(msg.value > 0){
emit Log(msg.value, gasleft());
}
}
// 读取_x
function getX() external view returns(uint x){
x = _x;
}
}传入合约地址
可以利用合约的地址和合约代码(或接口)来创建合约的引用:_Name(_Address),其中_Name是合约名,应与合约代码(或接口)中标注的合约名保持一致,_Address是合约地址。然后用合约的引用来调用它的函数:_Name(_Address).f(),其中f()是要调用的函数。
contract CallContract{
function callSetX(address _Address, uint256 x) external{
OtherContract(_Address).setX(x);
}
}传入合约变量
contract CallContract{
function callGetX(OtherContract _Address) external view returns(uint x){
x = _Address.getX();
}
}创建合约变量
contract CallContract{
function callGetX2(address _Address) external view returns(uint x){
OtherContract oc = OtherContract(_Address);
x = oc.getX();
}
}调用合约并发送ETH
如果目标合约的函数是payable的,那么我们可以通过调用它来给合约转账:_Name(_Address).f{value: _Value}(),其中_Name是合约名,_Address是合约地址,f是目标函数名,_Value是要转的ETH数额(以wei为单位)。
OtherContract合约的setX函数是payable的,在下面这个例子中我们通过调用setX来往目标合约转账。
contract CallContract{
function setXTransferETH(address otherContract, uint256 x) payable external{
OtherContract(otherContract).setX{value: msg.value}(x);
}
}Call
call不仅可以发送ETH,还可以调用合约
我先说一下我的情况: 我现在从事网络安全的工作,但工作之余我想学习web3相关的东西,目前已经知道的东西:BTC的钱包的原理,包括私钥公钥生成这种,直接我都写代码实现过;目前知道solidity的基础语法,除此之外,关于web3的其他就都不知道了,主要是知道的一些其他的东西都是碎片化、孤岛化的,并没有系统的串联起来,给我一些具体的学习路线,我知道web3东西很多,但给我一些具体的学习路线