函数
[TOC]
函数
存储类型: storage、memory、calldata
- storage: 状态变量,修改了 storage 变量的值会持久的存储在区块链上
- memory: 局部变量,函数运行结束后,变量就会销毁
- calldata: 局部变量,但是只能用于输入参数中
调用其他合约的方法
实例化调用合约的方式
pragma solidity ^0.8.3;
contract CallTestContract {
function setX(address _test, uint _x) external {
TestContract(_test).setX(_x);
}
// 方法二
// function setX(TestContract _test,uint _x) external {
// _test.setX(_x);
// }
// 写法一
// function getX(address _test) external view returns (uint) {
// return TestContract(_test).getX();
// }
// 写法二
function getX(address _test) external view returns(uint x) {
x = TestContract(_test).getX();
}
// 调用方法并发送ETH
function setXandReceiveEther(address _test,uint _x) external payable {
TestContract(_test).setXandReceiveEther{value: msg.value}(_x);
}
function getXandValue(address _test) external view returns(uint x,uint value) {
(x,value) = TestContract(_test).getXandValue();
}
}
contract TestContract {
uint public x;
uint public value = 123;
function setX(uint _x) external {
x = _x;
}
function getX() external view returns(uint) {
return x;
}
function setXandReceiveEther(uint _x) external payable {
x = _x;
value = msg.value;
}
function getXandValue() external view returns(uint,uint) {
return (x,value);
}
}
call
A call B, send 100 wei
B call C, send 50 wei
C 合约视角:
msg.sender = B
msg.value = 50 wei
C 如果有改变状态变量的逻辑,则会改变自身的状态变量
B 合约发送给 C 合约的 ETH 会留在 C 合约中
// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.3;
contract TestCall {
string public message;
uint public x;
event Log(string message);
fallback() external payable {
emit Log("fallback was called");
}
receive() external payable {}
function foo(string memory _message,uint _x) external payable returns(bool,uint) {
message = _message;
x = _x;
return (true,999);
}
}
contract Call {
bytes public data;
function callFoo(address _test) external payable {
(bool success,bytes memory _data) = _test.call{value: 23000}(
abi.encodeWithSignature("foo(string,uint256)","call foo",123)
);
data = _data;
require(success,"call failed");
}
function callDoesNotExit(address _test) external {
(bool success,) = _test.call(abi.encodeWithSignature("doesNotExit()"));
require(success,"call failed");
}
}
delegatecall
A call B, send 100 wei
B delegatecall C
C 合约视角
msg.sender = A
msg.value = 100 wei
C 合约不能修改自身的状态变量,C 合约仅执行逻辑,修改的是 B 合约的状态变量。要求 B 合约的状态变量的书写顺序和 C 合约严格一致。C 合约可以在 B 合约状态变量的基础上再增加自己的状态变量。
C 合约也不能保存 ETH,ETH 也保存在 B 合约中
示例:
pragma solidity ^0.8.3;
contract TestDelegateCall {
uint public num;
address public sender;
uint public value;
// 前三个变量的位置与TestDelegateCall必须保证严格一致,但是可以在下面增加自己的变量
address public owner;
function setVars(uint _num) external payable {
num = 2 * _num;
sender = msg.sender;
value = msg.value;
}
}
contract DelegateCall {
uint public num;
address public sender;
uint public value;
function setVars(address _test,uint _num) external payable {
// (bool success,bytes memory data) = _test.delegatecall()
(bool success,) = _test.delegatecall(
abi.encodeWithSelector(TestDelegateCall.setVars.selector,_num)
);
require(success,"delegatecall failed");
}
}
fallback 和 receive
fallback
函数主要有两个作用
- 当调用的函数在合约中不存在时,会自动触发
fallback
- 向合约直接发送主币 ETH
定义方法
pragma solidity ^0.8.3;
contract Fallback {
// 当调用合约中不存在的方法时,会自动触发 fallback 函数
// fallback() external {}
// 加上了payable以后,就可以直接给合约发送ETH了
fallback() external payable {}
// 如果只是为了接收ETH,可以直接使用receive
receive() external payable {}
}
/\*
fallback() 和 receive()的执行顺序
Ether is send to contract
|
is msg.data empty?
/ \
yes no
/ \
receive() exists? fallback()
/ \
yes no
/ \
receive() fallback()
\*/
函数修改器(modifier)
- 使用 修改器 modifier 可以轻松改变函数的行为
- 修改器 modifier 是合约的可继承属性,并可能被派生合约覆盖 , 但前提是它们被标记为 virtual。
- 如果你想访问定义在合约 C 的 修改器 modifier m , 可以使用 C.m 去引用它
- 只能使用在当前合约或在基类合约中定义的 修改器 modifier , 修改器 modifier 也可以定义在库里面,但是他们被限定在库函数使用。
- 如果同一个函数有多个 修改器 modifier,它们之间以空格隔开,修改器 modifier 会依次检查执行。
- 修改器不能隐式地访问或改变它们所修饰的函数的参数和返回值。 这些值只能在调用时明确地以参数传递。
- 在函数修改器中,指定何时运行被修改器应用的函数是有必要。占位符语句(用单个下划线字符 _ 表示)用于表示被修改的函数的主体应该被插入的位置。
- 修改器 modifier 或函数体中显式的 return 语句仅仅跳出当前的 修改器 modifier 和函数体
_
符号可以在修改器中出现多次,每处都会替换为函数体。
例如:
contract owned {
constructor() { owner = payable(msg.sender); }
address owner;
modifier onlyOwner {
require(
msg.sender == owner,
"Only owner can call this function."
);
_;
}
}
contract destructible is owned {
function destroy() public onlyOwner {
selfdestruct(owner);
}
}
contract priced {
// 修改器可以接收参数:
modifier costs(uint price) {
if (msg.value >= price) {
_;
}
}
}
contract Register is priced, destructible {
mapping (address => bool) registeredAddresses;
uint price;
constructor(uint initialPrice) { price = initialPrice; }
// 在这里也使用关键字 `payable` 非常重要,否则函数会自动拒绝所有发送给它的以太币。
function register() public payable costs(price) {
registeredAddresses[msg.sender] = true;
}
function changePrice(uint price_) public onlyOwner {
price = price_;
}
}
函数签名
在 Solidity 中,函数签名是根据函数的名称和参数类型生成的唯一标识符。你可以使用 Solidity 编译器或 Web3.js 等库来计算函数的签名。
如果你使用 Solidity 编译器,可以通过以下步骤计算函数签名:
使用
keccak256
哈希函数计算函数的规范签名。规范签名的格式为functionName(argumentType1, argumentType2, ...) returns (returnType)
,其中functionName
是函数名称,argumentType1, argumentType2, ...
是参数类型,returnType
是返回类型。注意,在计算规范签名时,无需包括参数的名称。取规范签名的前 4 个字节(8 个十六进制字符),这是函数签名的部分。
以下是一个示例,演示如何使用 Solidity 编译器计算函数签名:
pragma solidity ^0.8.0;
contract SignatureExample {
function myFunction(uint256 param1, address param2) external pure returns (bool) {
// 函数体
return true;
}
}
contract SignatureCalculator {
function calculateSignature() external pure returns (bytes4) {
// 计算函数签名
bytes4 signature = bytes4(keccak256("myFunction(uint256,address)"));
return signature;
}
}
上述示例中,calculateSignature
函数使用keccak256
哈希函数在SignatureExample
合约中的myFunction
函数的规范签名上进行计算,并返回函数签名。
请注意,函数签名通常用于与 ABI 交互,以便正确识别和调用指定的函数。
ethers 生成 function 签名和 bytes4
示例交易: https://etherscan.io/tx/0x833708fff6ffc99caffc759807b25b669e0dcdd43bd3be11dd86e1db9cfd639d
- ethersV5
import { utils } from "ethers";
const functionSign = utils.id("execute(bytes,bytes[],uint256)");
const functionSignatureBytes4 = functionSign.slice(0, 10);
console.log(
`function signature: ${functionSign} methodId: ${functionSignatureBytes4}`
);
------------output
function signature: 0x3593564c0e03854874915063081d274d42c12a434f5408440e6c17bae354ee79
methodId: 0x3593564c
- ethersV6
import { id } from "ethers";
const functionSign = id("execute(bytes,bytes[],uint256)");
const functionSignatureBytes4 = functionSign.slice(0, 10);
console.log(
`function signature: ${functionSign} methodId: ${functionSignatureBytes4}`
);
------------output
function signature: 0x3593564c0e03854874915063081d274d42c12a434f5408440e6c17bae354ee79
methodId: 0x3593564c