
加密小镇上有两家店:
- A 店(水果店) :老板是 Alice,有自己的账本(合约存储),记录着 "苹果库存"(存储变量
uint256 public appleStock = 100;),但没学会 "盘点库存""修改库存" 的方法; - B 店(管理咨询店) :老板是 Bob,专门帮人做库存管理,有两套核心 "操作手册"(合约函数):
checkStock():读取自己账本上的库存,返回数值;addStock(uint256 num):把自己账本上的库存增加num个。
A 店想复用 B 店的方法,不用自己写代码 ------ 这就对应 Solidity 里合约间的两种调用方式:call和delegatecall。
一、先看 "联系":都是 "借力办事",复用他人逻辑
不管是call还是delegatecall,核心目的都是让 A 店(调用方合约)借用 B 店(被调用方合约)的代码逻辑,不用自己重复开发。就像 A 店不用自己学盘点方法,直接请 B 店的人来 "帮忙操作",本质都是 "复用他人功能"。
另外,两者的基础规则一致:
- 都会传递
msg.sender(调用者身份,比如 Alice 发起调用,msg.sender就是 Alice)和msg.value(附带的 ETH); - 都需要知道 B 店的地址和函数签名(比如
checkStock()的签名是0x123...); - 都是合约间交互的核心方法,属于低级别调用(区别于
合约实例.函数名()的高级调用)。
二、核心区别:"借方法时,用谁的账本?"
这是call和delegatecall的本质差异 ------调用方是否使用自己的存储(账本)执行被调用方的代码。
场景 1:call 调用 ------"借你的方法,算你的账"
Alice 想让 B 店帮忙 "盘点库存",用了call方式:
- Alice 对 B 店说:"麻烦用你的
checkStock()方法,帮我看看库存有多少?" - B 店收到请求后,拿出自己的账本(B 店的存储),发现自己的 "苹果库存" 是 0(B 店本来不存水果),于是回复 Alice:"库存是 0";
- 后续 Alice 又用
call调用 B 店的addStock(50):- B 店还是用自己的账本,把自己的库存从 0 改成 50;
- A 店的账本丝毫没变化,苹果库存依然是 100。
对应 Solidity 逻辑:
// A店合约(调用方)
contract ShopA {
uint256 public appleStock = 100; // A店自己的库存(账本)
function callBCheckStock(address shopB) external returns (uint256) {
// call调用B店的checkStock(),用B店的存储
(bool success, bytes memory data) = shopB.call(abi.encodeWithSignature("checkStock()"));
require(success, "call failed");
return abi.decode(data, (uint256)); // 返回B店的库存(0)
}
function callBAddStock(address shopB) external {
// call调用B店的addStock(50),修改B店的存储
shopB.call(abi.encodeWithSignature("addStock(uint256)", 50));
}
}
// B店合约(被调用方)
contract ShopB {
uint256 public appleStock = 0; // B店自己的库存(账本)
function checkStock() external view returns (uint256) {
return appleStock; // 读取B店的存储
}
function addStock(uint256 num) external {
appleStock += num; // 修改B店的存储
}
}
call 的核心特点 :被调用方(B 店)的代码,操作的是被调用方自己的存储,调用方(A 店)的存储完全不受影响 ------ 相当于 "借别人的工具,修别人的东西"。
场景 2:delegatecall 调用 ------"借你的方法,修我的账"
Alice 觉得call没用(改的是 B 店的库存),于是换了delegatecall方式:
- Alice 对 B 店说:"麻烦用你的
checkStock()方法,但帮我查我自己的账本!" - B 店收到请求后,没有拿自己的账本,而是拿起A 店的账本(A 店的存储),读取 A 店的苹果库存 100,回复 Alice:"库存是 100";
- 后续 Alice 用
delegatecall调用 B 店的addStock(50):- B 店依然用 A 店的账本,把 A 店的库存从 100 改成 150;
- B 店自己的账本还是 0,丝毫没变化。
对应 Solidity 逻辑(B 店合约不变,A 店调用方式改):
contract ShopA {
uint256 public appleStock = 100; // A店自己的库存(账本)
function delegateCallBCheckStock(address shopB) external returns (uint256) {
// delegatecall调用B店的checkStock(),用A店的存储
(bool success, bytes memory data) = shopB.delegatecall(abi.encodeWithSignature("checkStock()"));
require(success, "delegatecall failed");
return abi.decode(data, (uint256)); // 返回A店的库存(100)
}
function delegateCallBAddStock(address shopB) external {
// delegatecall调用B店的addStock(50),修改A店的存储
shopB.delegatecall(abi.encodeWithSignature("addStock(uint256)", 50));
}
}
delegatecall 的核心特点 :被调用方(B 店)的代码,操作的是调用方(A 店)的存储------ 相当于 "借别人的工具,修自己的东西"。
⚠️ 关键注意点:delegatecall要求 "调用方和被调用方的存储结构对齐"!比如 A 店和 B 店都必须有uint256 public appleStock(且变量顺序一致),否则会读取 / 修改错误的存储位置(比如把 A 店的其他变量当成库存改了)。
三、区别与联系总结表
| 维度 | call | delegatecall |
|---|---|---|
| 核心联系 | 均为合约间低级别调用,复用被调用方代码逻辑,传递 msg.sender/msg.value | |
| 操作的存储 | 被调用方(B 店)的存储 | 调用方(A 店)的存储 |
| 核心用途 | 调用其他合约的功能,且需要修改对方的状态(比如调用 Uniswap 的 swap 函数,修改 Uniswap 的流动性) | 复用通用逻辑(如权限校验、数学计算),且需要修改自身状态(比如多个合约共用 "修改所有者" 的逻辑,用 delegatecall 调用代理合约的代码,修改自身的 owner) |
| 存储要求 | 无强制对齐要求(调用方和被调用方存储可不同) | 必须对齐存储结构(变量类型、顺序一致),否则会出现存储错乱 |
| 场景类比 | 请维修师傅用他的工具,修他的设备 | 请维修师傅用他的工具,修你的设备 |
四、真实开发中的典型场景
-
call 的常见场景:
- 调用其他 DApp 的合约功能(比如调用 USDT 的
transfer转账,修改 USDT 合约中的余额记录); - 批量执行多个合约操作(比如一次调用多个合约的查询方法)。
- 调用其他 DApp 的合约功能(比如调用 USDT 的
-
delegatecall 的常见场景:
- 代理模式(Proxy Pattern):比如升级合约时,代理合约(用户交互的合约)用
delegatecall调用逻辑合约(存储业务代码),确保用户数据始终存在代理合约中,逻辑合约可替换; - 通用逻辑复用(比如多个合约都需要 "只有管理员能操作" 的权限校验,把校验逻辑写在一个 "权限合约" 里,其他合约用
delegatecall调用,修改自身的管理员状态)。
- 代理模式(Proxy Pattern):比如升级合约时,代理合约(用户交互的合约)用
简单说:call是 "帮别人办事",delegatecall是 "请别人帮自己办事"------ 核心区别就在 "办事时用谁的账本(存储)"。