目录
引言
在以太坊智能合约的开发中,传统的代币转账方式通常依赖于用户直接调用 transfer 或 transferFrom 方法。然而,这种方式在某些场景下可能不够灵活,尤其是当需要第三方代表用户执行代币操作时。为了解决这一问题,引入了“操作员(Operator)”机制,使得用户可以授权第三方在特定条件下代表自己进行代币操作。
在 OpenZeppelin 的 Confidential Fungible Token(CFT)实现中,除了传统的转账方式,还引入了带回调(callback)的转账函数 confidentialTransferWithCallback。这种方式允许在代币转账完成后,自动触发接收方合约的特定函数,从而实现更复杂的业务逻辑。
本文将详细介绍操作员机制的实现方式,以及带回调的转账函数如何在发送方和接收方合约中协同工作,实现代币的安全转移和自动处理。
一、Operator
在标准的 ERC-20 代币合约中,用户只能直接调用 transfer 和 transferFrom 方法来转移代币。然而,在某些场景下,我们希望允许第三方代表用户进行代币转移操作,这时就引入了操作员机制。操作员是被授权代表用户执行特定操作的地址,通常具有一定的权限和有效期限。
const alice: Wallet;
const expirationTimestamp = Math.round(Date.now() / 1000) + 60 * 60 * 24; // 当前时间 + 24 小时
await tokenContract.connect(alice).setOperator(bob, expirationTimestamp);
代码解释
创建一个钱包实例 alice,用于签名交易。
计算当前时间加上 24 小时后的时间戳,作为操作权限的过期时间。
通过 alice 钱包连接到代币合约,并调用 setOperator 方法,将 bob 设置为操作员,权限在指定的时间戳后过期。
二、Callback
回调就是转账完成后,“自动触发”对接收方合约的特定函数进行调用。
如果你选择使用带回调的转账函数,就不需要再手动调用中间授权(如“操作员审批”)步骤,系统会自动处理。
被回调函数调用的智能合约必须实现 IConfidentialFungibleTokenReceiver 接口。
发送方合约(Token 合约)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IConfidentialFungibleTokenReceiver} from "@openzeppelin/confidential-contracts/interfaces/IConfidentialFungibleTokenReceiver.sol";
import {ConfidentialFungibleToken} from "@openzeppelin/confidential-contracts/token/ConfidentialFungibleToken.sol";
contract ConfidentialToken is ConfidentialFungibleToken {
constructor() ConfidentialFungibleToken("ConfidentialToken", "CFT") {}
function confidentialTransferWithCallback(
address recipient,
externalEuint64 encryptedAmount,
bytes calldata inputProof,
bytes calldata callbackData
) external {
confidentialTransferFrom(msg.sender, address(this), encryptedAmount, inputProof);
bool success = IConfidentialFungibleTokenReceiver(recipient)
.onConfidentialTransferReceived(
msg.sender,
encryptedAmount,
callbackData
);
require(success, "Callback failed");
}
}
接收方合约(Receiver)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {IConfidentialFungibleTokenReceiver} from "@openzeppelin/confidential-contracts/interfaces/IConfidentialFungibleTokenReceiver.sol";
contract Receiver is IConfidentialFungibleTokenReceiver {
mapping(address => uint256) public balances;
function onConfidentialTransferReceived(
address from,
bytes calldata ciphertext,
bytes calldata data
) external override returns (ebool) {
uint256 decryptedAmount = 100; // 假设解密后的金额为 100
balances[from] += decryptedAmount;
return true;
}
}
代码解释
发送方
调用 confidentialTransferWithCallback:将加密金额转账给接收方合约。
Token 合约自动调用回调函数:在转账完成后,自动调用接收方合约
的 onConfidentialTransferReceived 方法。
接收方合约处理回调:接收方合约
在 onConfidentialTransferReceived 方法中进行加密金额的解密和验证。如果验证通过,更新接收方的余额并返回 true;否则,返回 false 或抛出异常,导致转账失败。
三、Swaps
(一)Swap ERC20 to ConfidentialFungibleToken
下面这段 Solidity 代码展示了一个将普通 ERC20 代币 **“包裹”(wrap)**成保密形式加密代币的过程。它主要做两件事:扣除用户的明文代币,然后 mint(铸造)等量的加密代币。
ERC20 代币仅需直接转入,若转入失败则交易自动回滚。随后,我们通过内部的 _mint 函数转出正确数量的 ConfidentialFungibleToken
,该函数保证转出操作必成功。
function wrap(address to, uint256 amount) public virtual {
// take ownership of the tokens
SafeERC20.safeTransferFrom(underlying(), msg.sender, address(this), amount - (amount % rate()));
// mint confidential token
_mint(to, (amount / rate()).toUint64().asEuint64());
}
代码解释
1、函数签名及参数
wrap(address to, uint256 amount):
to 是接收新铸造的加密代币的地址;
amount 是用户想包裹的明文 ERC20 代币数量。
2、扣除用户明文代币
underlying()
返回被包裹的原始 ERC20 代币合约地址;
safeTransferFrom
从调用者 msg.sender
扣除 (amount - (amount % rate()))
数量的明文代币,转入当前合约;
amount % rate()
残余部分被抹去,确保只扣整倍的代币;
rate() 是一个兑换比例,比如 100:1 ——意味着你需要 100 个明文资产对应 1 个加密代币。
3、铸造对应加密代币
计算铸造数量:amount / rate()
,得到你能获取多少单位的加密代币;
toUint64()
:确保铸造数量不超过 uint64 的最大值;
.asEuint64()
:将普通整数转换为加密整数类型 euint64;
_mint(to, …)
将等量的加密代币(保密状态)铸造/记账给 to 地址。
(二)Swap ConfidentialFungibleToken to ConfidentialFungibleToken
Solidity 代码实现了一个基于 Zama FHE 协议的加密代币交换功能,允许用户在链上进行隐私保护的代币交换。在 Zama 的 FHE 协议中,所有数据都是加密的,只有授权的合约才能解密和操作这些数据。
function swapConfidentialForConfidential(
IConfidentialFungibleToken fromToken,
IConfidentialFungibleToken toToken,
externalEuint64 amountInput,
bytes calldata inputProof
) public virtual {
require(fromToken.isOperator(msg.sender, address(this)));
euint64 amount = FHE.fromExternal(amountInput, inputProof);
FHE.allowTransient(amount, address(fromToken));
euint64 amountTransferred = fromToken.confidentialTransferFrom(msg.sender, address(this), amount);
FHE.allowTransient(amountTransferred, address(toToken));
toToken.confidentialTransfer(msg.sender, amountTransferred);
}
代码解释
①require(fromToken.isOperator(msg.sender, address(this)));
作用:验证调用者(msg.sender)是否被授权为 fromToken 的操作员。
背景:在 Zama 的 FHE 协议中,只有被授权的操作员才能执行加密代币的转账操作。
② euint64 amount = FHE.fromExternal(amountInput, inputProof);
作用:将外部传入的加密数据(amountInput)和证明(inputProof)转换为内部可操作的加密整数类型 euint64。
背景:euint64 是 Zama FHE 协议中用于表示加密整数的类型。
③ FHE.allowTransient(amount, address(fromToken));
作用:允许对 fromToken 合约地址的临时访问权限,以便执行加密代币的转账操作。
背景:在 FHE 协议中,操作加密数据需要临时授权。
④euint64 amountTransferred = fromToken.confidentialTransferFrom(msg.sender, address(this), amount);
作用:从调用者(msg.sender)向当前合约(address(this))转移指定数量的加密代币。
背景:confidentialTransferFrom 是 Zama FHE 协议中用于执行加密代币转账的函数。
⑤ FHE.allowTransient(amountTransferred, address(toToken));
作用:允许对 toToken 合约地址的临时访问权限,以便将转账的加密代币发送给目标地址。
背景:在 FHE 协议中,操作加密数据需要临时授权。
⑥ toToken.confidentialTransfer(msg.sender, amountTransferred);
作用:将转账的加密代币发送到目标地址(msg.sender)。
背景:confidentialTransfer 是 Zama FHE 协议中用于执行加密代币转账的函数。
(三)Swap ConfidentialFungibleToken to ERC20
从保密代币切换到非保密代币是最复杂的操作,因为需要访问解密后的数据才能准确完成请求。在我们的示例中,解密操作将在链下进行,并通过 Zama 的网关将结果回传。以下是一个合约示例,演示如何将保密代币与 ERC20 代币进行 1:1 兑换。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
// 导入 Zama FHE 库,用于处理加密整数类型
import {FHE, externalEuint64, euint64} from "@fhevm/solidity/lib/FHE.sol";
// 导入标准 ERC20 接口
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
// 导入 SafeERC20,用于安全转账
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
// 导入保密代币接口
import {IConfidentialFungibleToken} from "@openzeppelin/confidential-contracts/interfaces/IConfidentialFungibleToken.sol";
contract SwapConfidentialToERC20 {
using FHE for *;
// 自定义错误:无效的解密回调请求 ID
error SwapConfidentialToERC20InvalidGatewayRequest(uint256 requestId);
// 映射:存储每个请求 ID 对应的接收者地址
mapping(uint256 => address) private _receivers;
// 保密代币合约(输入端)
IConfidentialFungibleToken private _fromToken;
// 普通 ERC-20 代币合约(输出端)
IERC20 private _toToken;
// 构造函数:初始化输入和输出代币合约地址
constructor(IConfidentialFungibleToken fromToken, IERC20 toToken) {
_fromToken = fromToken;
_toToken = toToken;
}
/**
* @notice 用户调用此函数发起 swap,将加密代币提交;
* @param encryptedInput 加密金额,externalEuint64 类型
* @param inputProof 加密金额的零知识证明
*/
function swapConfidentialToERC20(externalEuint64 encryptedInput, bytes memory inputProof) public {
// 1. 使用 proof 将加密输入转为链上可用的加密整数
euint64 amount = encryptedInput.fromExternal(inputProof);
// 2. 授权保密代币合约读取 this 合约中的这笔加密金额
amount.allowTransient(address(_fromToken));
// 3. 从用户 transfer 保密代币到本合约
euint64 amountTransferred = _fromToken.confidentialTransferFrom(msg.sender, address(this), amount);
// 4. 将 ciphertext 添加到解密请求列表
bytes32 ;
cts[0] = euint64.unwrap(amountTransferred);
// 5. 发出解密请求,并指定回调函数 finalizeSwap
uint256 requestID = FHE.requestDecryption(cts, this.finalizeSwap.selector);
// 6. 保存 requestID ↔ 用户地址 关联,等待回调发币
_receivers[requestID] = msg.sender;
}
/**
* @notice 解密服务调用此函数,将加密金额转成明文后,合约会发放同等 ERC-20 代币
* @param requestID 与 swap 请求对应的唯一 ID
* @param amount 解密后拿到的金额(uint64 明文)
* @param signatures 多重签名验证解密合法性
*/
function finalizeSwap(uint256 requestID, uint64 amount, bytes[] memory signatures) public virtual {
// 1. 验证解密签名是否合法
FHE.checkSignatures(requestID, signatures);
// 2. 获取之前保存的接收方
address to = _receivers[requestID];
if (to == address(0)) revert SwapConfidentialToERC20InvalidGatewayRequest(requestID);
// 3. 清除记录避免重复提现
delete _receivers[requestID];
// 4. 如果解密金额大于 0,将 ERC-20 转给接收者
if (amount != 0) {
SafeERC20.safeTransfer(_toToken, to, amount);
}
}
}
代码解释
注释写在代码上啦。这段 Solidity 合约 SwapConfidentialToERC20 用于将保密代币(encrypted token)兑换成普通 ERC‑20 代币,核心思路是:用户先将加密金额转移到合约,触发链下解密过程,最后由合约释放等值的 ERC‑20。它做了三件事:接收保密代币、提交解密请求、解密成功后,释放相应 ERC‑20 代币。
总结
通过引入操作员机制和带回调的转账函数,智能合约的代币操作变得更加灵活和安全。
操作员机制
:用户可以通过 setOperator 方法授权第三方在指定时间内代表自己执行代币操作。这种方式避免了频繁的授权操作,提高了用户体验。
带回调的转账函数
:发送方合约在执行代币转账后,自动调用接收方合约的 onConfidentialTransferReceived 方法,实现自动处理。这种方式简化了授权和转账的流程,提高了系统的自动化程度。
通过上述机制的结合,智能合约不仅实现了代币的安全转移,还能够在转账完成后自动触发特定的业务逻辑,为去中心化应用(DApp)提供了更强大的功能支持。