Balancer池用户授权方法及BAL#401错误解决咨询
解决Balancer池授权问题(BAL#401错误)
问题根源
BAL#401错误说明你的BalancerOperator合约没有被授权调用Balancer Vault的joinPool、exitPool、swap等核心操作。Balancer V2的权限由TimelockAuthorizer合约统一管理,必须为你的Operator合约配置对应操作的权限才能执行交易。
授权步骤与核心函数
你使用的主网Authorizer地址为0xa331d84ec860bf466b4cdccfb4ac09a1b43f3ae6,授权需分两步完成:
1. 生成操作权限标识符(Action ID)
每个Vault操作对应唯一的Action ID,通过对函数签名做keccak256哈希生成:
- 加入流动性(joinPool):
bytes32 joinPoolActionId = keccak256("joinPool(bytes32,address,address,(address[],uint256[],bytes,bool))"); - 退出流动性(exitPool):
bytes32 exitPoolActionId = keccak256("exitPool(bytes32,address,address,(address[],uint256[],bytes,bool))"); - 代币交换(swap):
bytes32 swapActionId = keccak256("swap((bytes32,enum(SwapKind),address,address,uint256,bytes),(address,bool,address,bool),uint256,uint256)");
2. 调用Authorizer的授权函数
在Hardhat分叉环境中,你需要模拟Authorizer管理员账户(主网多签账户),调用grantPermission函数为你的Operator合约授权:
function grantPermission(bytes32 actionId, address account, address target) external;
参数说明:
actionId:对应操作的权限标识符account:需要授权的地址(你的BalancerOperator合约地址)target:被授权操作的目标合约(Vault地址0xBA12222222228d8Ba445958a75a0704d566BF2C8)
Hardhat测试脚本示例
const { ethers } = require("hardhat"); async function main() { // 模拟主网管理员账户(分叉环境中使用) const adminAddr = "0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f"; await hre.network.provider.request({ method: "hardhat_impersonateAccount", params: [adminAddr], }); const adminSigner = await ethers.getSigner(adminAddr); // 加载Authorizer合约 const authorizer = await ethers.getContractAt( "IAuthorizer", "0xa331d84ec860bf466b4cdccfb4ac09a1b43f3ae6", adminSigner ); const vaultAddr = "0xBA12222222228d8Ba445958a75a0704d566BF2C8"; const operatorAddr = "你的BalancerOperator合约部署地址"; // 授权joinPool权限 const joinPoolActionId = ethers.utils.keccak256( ethers.utils.toUtf8Bytes("joinPool(bytes32,address,address,(address[],uint256[],bytes,bool))") ); await authorizer.grantPermission(joinPoolActionId, operatorAddr, vaultAddr); // 授权exitPool权限 const exitPoolActionId = ethers.utils.keccak256( ethers.utils.toUtf8Bytes("exitPool(bytes32,address,address,(address[],uint256[],bytes,bool))") ); await authorizer.grantPermission(exitPoolActionId, operatorAddr, vaultAddr); // 授权swap权限 const swapActionId = ethers.utils.keccak256( ethers.utils.toUtf8Bytes("swap((bytes32,enum(SwapKind),address,address,uint256,bytes),(address,bool,address,bool),uint256,uint256)") ); await authorizer.grantPermission(swapActionId, operatorAddr, vaultAddr); console.log("权限授权完成"); } main().catch((error) => { console.error(error); process.exitCode = 1; });
关键注意事项
- 每个Vault操作需要单独授权,不能一次性批量授权所有权限。
- 分叉环境中必须模拟管理员账户才能调用
grantPermission,主网环境需通过Balancer多签账户执行授权。
合约优化建议
你的BalancerOperator无需继承IVault,直接通过接口调用更简洁:
contract BalancerOperator { IVault private immutable _vault; constructor(address vaultAddr) { _vault = IVault(vaultAddr); } // 加入流动性函数 function addLiquidity( bytes32 poolId, address[] calldata assets, uint256[] calldata maxAmountsIn ) external { bytes memory userData = abi.encode(1, maxAmountsIn, 1); IVault.JoinPoolRequest memory request = IVault.JoinPoolRequest(assets, maxAmountsIn, userData, false); _vault.joinPool(poolId, msg.sender, msg.sender, request); } // 其余函数逻辑保持不变,调用时直接使用_vault.exitPool(...)、_vault.swap(...) }
内容的提问来源于stack exchange,提问作者Ignacio Ceaglio
相关产品推荐
相关产品推荐

