[Proposal] Authorize the Batch Relayer

This proposal aims to authorize a relayer capable of atomically combining sequences of swaps , joins and exits , in addition to wrapping and unwrapping stETH and Aave aTokens. The relayer would be granted authority to perform the following tasks on behalf of users who have opted into using it :

  • Leverage the user’s existing Vault allowances and internal balances to avoid duplicate approvals.
  • Swap on the user’s behalf.
  • Add and remove liquidity on the user’s behalf.

Motivation

As nesting becomes a more relevant piece of the Balancer Ecosystem, executing certain operations requires not only a combination of swaps, but also of joins and exits. The Balancer Vault natively supports batching swaps, but not joins/exits. Consider for example the case of a liquidity provider addding liquidity to a Weighted Pool made of BAL and staBAL3 :

  1. use any combination of DAI, USDC and USDT to join the staBAL3 pool, getting staBAL3 tokens out;
  2. join the BAL/staBAL3 Weighted Pool with BAL and staBAL3 from step 1;

The relayer seeking authorization in this proposal is designed to ease this UX burden by creating an interface by which users can combine swaps, joins, exits, wrappings and unwrappings in any order.

Risks

This proposal would only grant the required roles to the BalancerRelayer contract. Each user would still be required to opt into the relayer by submitting an approval transaction or signing a message.

The code is a simple wrapper over existing Balancer pool interactions, and it has been thoroughly tested and reviewed by the Balancer Labs team.

The addresses of the BalancerRelayer mentioned in this proposal can be found in the official repo.

Specification

The Balancer governance multisig on each network would submit a transaction to the Authorizer in order to grant the following roles to the BalancerRelayer:

  1. manageUserBalance : Utilize existing Vault allowances and internal balances so that a user does not have to re-approve the new relayer for each token.
  2. joinPool : Add liquidity to a pool on the user’s behalf.
  3. exitPool : Remove liquidity from a pool on the user’s behalf.
  4. swap : Trade within a single pool on the user’s behalf.
  5. batchSwap : Make a multihop trade or source liquidity from multiple pools.
  6. setRelayerApproval : Approve the relayer on the user’s behalf (user must still provide a signed message).

Ethereum Mainnet

The Gnosis Safe at 0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f would send a transaction to the Vault Authorizer at 0xA331D84eC860Bf466b4CdCcFb4aC09a1B43F3aE6 with the following data to authorize the BalancerRelayer at 0xAc9f49eF3ab0BbC929f7b1bb0A17E1Fca5786251 :

0xfcd7627e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000ac9f49ef3ab0bbc929f7b1bb0a17e1fca578625100000000000000000000000000000000000000000000000000000000000000060014a06d322ff07fcc02b12f93eb77bb76e28cdee4fc0670b9dec98d24bbfec8eba777d811cd36c06d540d7ff2ed18ed042fd67bbf7c9afcf88c818c7ee6b49878ad1b68d148c070372f8643c4648efbb63c6a8a338f3c24714868e791367653c149e88b59429ded7f601ab52ecd62331cac006ae07c16543439ed138dcb8d347b8a1d293670124924a0f532213753b89db10bde737249d4540e9a03657d1aff1282ab709b2b70070f829c46bc36f76b32ad4989fecb2fcb09a1b3ce00bbfc30

Which is the ABI-encoded calldata for:

authorizer.grantRoles(
  [
    0x0014a06d322ff07fcc02b12f93eb77bb76e28cdee4fc0670b9dec98d24bbfec8,
    0xeba777d811cd36c06d540d7ff2ed18ed042fd67bbf7c9afcf88c818c7ee6b498,
    0x78ad1b68d148c070372f8643c4648efbb63c6a8a338f3c24714868e791367653,
    0xc149e88b59429ded7f601ab52ecd62331cac006ae07c16543439ed138dcb8d34,
    0x7b8a1d293670124924a0f532213753b89db10bde737249d4540e9a03657d1aff,
    0x1282ab709b2b70070f829c46bc36f76b32ad4989fecb2fcb09a1b3ce00bbfc30,
  ],
  0xAc9f49eF3ab0BbC929f7b1bb0A17E1Fca5786251
);

Polygon

The Gnosis Safe at 0xd2bD536ADB0198f74D5f4f2Bd4Fe68Bae1e1Ba80 would send a transaction to the Vault Authorizer at 0xA331D84eC860Bf466b4CdCcFb4aC09a1B43F3aE6 with the following data to authorize the BalancerRelayer at 0x4574ccBcC09A00C9eE55fB92Fe353699A4fA800e :

0xfcd7627e00000000000000000000000000000000000000000000000000000000000000400000000000000000000000004574ccbcc09a00c9ee55fb92fe353699a4fa800e00000000000000000000000000000000000000000000000000000000000000060014a06d322ff07fcc02b12f93eb77bb76e28cdee4fc0670b9dec98d24bbfec8eba777d811cd36c06d540d7ff2ed18ed042fd67bbf7c9afcf88c818c7ee6b49878ad1b68d148c070372f8643c4648efbb63c6a8a338f3c24714868e791367653c149e88b59429ded7f601ab52ecd62331cac006ae07c16543439ed138dcb8d347b8a1d293670124924a0f532213753b89db10bde737249d4540e9a03657d1aff1282ab709b2b70070f829c46bc36f76b32ad4989fecb2fcb09a1b3ce00bbfc30

Which is the ABI-encoded calldata for:

authorizer.grantRoles(
  [
    0x0014a06d322ff07fcc02b12f93eb77bb76e28cdee4fc0670b9dec98d24bbfec8,
    0xeba777d811cd36c06d540d7ff2ed18ed042fd67bbf7c9afcf88c818c7ee6b498,
    0x78ad1b68d148c070372f8643c4648efbb63c6a8a338f3c24714868e791367653,
    0xc149e88b59429ded7f601ab52ecd62331cac006ae07c16543439ed138dcb8d34,
    0x7b8a1d293670124924a0f532213753b89db10bde737249d4540e9a03657d1aff,
    0x1282ab709b2b70070f829c46bc36f76b32ad4989fecb2fcb09a1b3ce00bbfc30,
  ],
  0x4574ccBcC09A00C9eE55fB92Fe353699A4fA800e
);

Arbitrum

The Gnosis Safe at 0x6207ed574152496c9B072C24FD87cE9cd9E17320 would send a transaction to the Vault Authorizer at 0xA331D84eC860Bf466b4CdCcFb4aC09a1B43F3aE6 with the following data to authorize the BalancerRelayer at 0x466262c2a275aB106E54D95B5B04603e12b58cA1 :

0xfcd7627e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000466262c2a275ab106e54d95b5b04603e12b58ca100000000000000000000000000000000000000000000000000000000000000060014a06d322ff07fcc02b12f93eb77bb76e28cdee4fc0670b9dec98d24bbfec8eba777d811cd36c06d540d7ff2ed18ed042fd67bbf7c9afcf88c818c7ee6b49878ad1b68d148c070372f8643c4648efbb63c6a8a338f3c24714868e791367653c149e88b59429ded7f601ab52ecd62331cac006ae07c16543439ed138dcb8d347b8a1d293670124924a0f532213753b89db10bde737249d4540e9a03657d1aff1282ab709b2b70070f829c46bc36f76b32ad4989fecb2fcb09a1b3ce00bbfc30

Which is the ABI-encoded calldata for:

authorizer.grantRoles(
  [
    0x0014a06d322ff07fcc02b12f93eb77bb76e28cdee4fc0670b9dec98d24bbfec8,
    0xeba777d811cd36c06d540d7ff2ed18ed042fd67bbf7c9afcf88c818c7ee6b498,
    0x78ad1b68d148c070372f8643c4648efbb63c6a8a338f3c24714868e791367653,
    0xc149e88b59429ded7f601ab52ecd62331cac006ae07c16543439ed138dcb8d34,
    0x7b8a1d293670124924a0f532213753b89db10bde737249d4540e9a03657d1aff,
    0x1282ab709b2b70070f829c46bc36f76b32ad4989fecb2fcb09a1b3ce00bbfc30,
  ],
  0x466262c2a275aB106E54D95B5B04603e12b58cA1
);

Final Notes

For transparency, a developer could reproduce the encoded data by computing the following:

const ethers = require("ethers")

const authorizer = new ethers.utils.Interface([
  "function grantRoles(bytes32[] memory roles, address account)",
]);
const vault = new ethers.utils.Interface([
  "function setRelayerApproval(address, address, bool)",
  "function manageUserBalance((uint8, address, uint256, address, address)[])",
  "function joinPool(bytes32, address, address, (address[], uint256[], bytes, bool))",
  "function exitPool(bytes32, address, address, (address[], uint256[], bytes, bool))",
  "function swap((bytes32, uint8, address, address, uint256, bytes), (address, bool, address, bool), uint256, uint256)",
  "function batchSwap(uint8, (bytes32, uint256, uint256, uint256, bytes)[], address[], (address, bool, address, bool), int256[], uint256)",
]);

function roleId(address, sighash) {
  return ethers.utils.solidityKeccak256(["uint256", "bytes4"], [address, sighash])
}

const roles = ["setRelayerApproval", "manageUserBalance", "joinPool", "exitPool", "swap", "batchSwap"]
  .map(name => roleId("0xBA12222222228d8Ba445958a75a0704d566BF2C8", vault.getSighash(name)));

// Mainnet
let batchRelayer = "0xAc9f49eF3ab0BbC929f7b1bb0A17E1Fca5786251";
let data = authorizer.encodeFunctionData("grantRoles", [roles, batchRelayer]);

console.log(`\n\n\n-*-*-*-*-*-*-MAINNET-*-*-*-*-*-*-`);
console.log(`authorizer.grantRoles(
  [
    ${roles.map(role => `${role},`).join("\n    ")}
  ],
  ${batchRelayer}
);`);
console.log(data);


// Polygon
batchRelayer = "0x4574ccBcC09A00C9eE55fB92Fe353699A4fA800e";
data = authorizer.encodeFunctionData("grantRoles", [roles, batchRelayer]);

console.log(`\n\n\n-*-*-*-*-*-*-POLYGON-*-*-*-*-*-*-`);
console.log(`authorizer.grantRoles(
  [
    ${roles.map(role => `${role},`).join("\n    ")}
  ],
  ${batchRelayer}
);`);
console.log(data);

// Arbitrum
batchRelayer = "0x466262c2a275aB106E54D95B5B04603e12b58cA1";
data = authorizer.encodeFunctionData("grantRoles", [roles, batchRelayer]);

console.log(`\n\n\n-*-*-*-*-*-*-ARBITRUM-*-*-*-*-*-*-`);
console.log(`authorizer.grantRoles(
  [
    ${roles.map(role => `${role},`).join("\n    ")}
  ],
  ${batchRelayer}
);`);
console.log(data);
  • Yes
  • No

0 voters

bring on the nesting!

Not sure about this.