Skip to main content

函数

[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函数主要有两个作用

  1. 当调用的函数在合约中不存在时,会自动触发fallback
  2. 向合约直接发送主币 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 编译器,可以通过以下步骤计算函数签名:

  1. 使用keccak256哈希函数计算函数的规范签名。规范签名的格式为functionName(argumentType1, argumentType2, ...) returns (returnType),其中functionName是函数名称,argumentType1, argumentType2, ...是参数类型,returnType是返回类型。注意,在计算规范签名时,无需包括参数的名称。

  2. 取规范签名的前 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