Following the announcement of the Balancer-Gnosis Protocol, this proposal aims to authorize the Gnosis Protocol V2 settlement contract with the necessary roles to:
Gnosis Protocol enables batched trading using off-chain signed orders. This trading protocol would enable Balancer V2 users to:
Additionally, given that the vault supports modifying relayer permissions using off-chain signatures, it would be possible to offer users a completely “gas-less” experience, where a first-time user with existing Vault balances and/or allowances could trade on Gnosis Protocol V2 by only signing off-chain messages, with all required gas related being taken in the ERC20 tokens that are being traded.
This proposal would only add the required roles to the Gnosis Protocol V2 vault relayer contract. Users would still be required to opt-in by allowing Gnosis Protocol V2 to function as relayer on their behalf.
The Gnosis Protocol settlement contract requires an order signed by the user in order to execute a transfer of funds or a swap. Additionally, neither the transfer nor the swap will be performed if the amount does not respect the user’s limit price included in the signed order.
Furthermore, while this proposal aims to authorize the Gnosis Protocol V2 contracts to act as a relayer, users must still opt-in to enable trading on Gnosis Protocol V2 by setting a relayer allowance (or signing an off-chain message granting this permission).
The code has been carefully tested, peer-reviewed and fully audited. The full smart contract audit can be found in the Gnosis Protocol V2 contracts public repository on Github[1] and there is an ongoing bug bounty[2].
The Balancer governance multisig would submit a transaction to the the Authorizer contract in order to grant the following roles to the Gnosis Protocol V2 vault relayer contract:
manageUserBalance: this role would be used by GPv2 in order to transfer the “from” tokens for verified user orders to the settlement contract for batch trading. batchSwap: Perform direct swaps on behalf of users, enabling user orders that were signed off-chain to get matched directly with Balancer pools.
Specifically, the Gnosis Safe at 0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f[3] would send a transaction to the Vault Authorizer at 0xA331D84eC860Bf466b4CdCcFb4aC09a1B43F3aE6[4] with the following data to authorize the Gnosis Protocol V2 Vault Relayer at 0xC92E8bdf79f0507f65a392b0ab4667716BFE0110[5]:
0xfcd7627e0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000c92e8bdf79f0507f65a392b0ab4667716bfe01100000000000000000000000000000000000000000000000000000000000000002eba777d811cd36c06d540d7ff2ed18ed042fd67bbf7c9afcf88c818c7ee6b4981282ab709b2b70070f829c46bc36f76b32ad4989fecb2fcb09a1b3ce00bbfc30
Which is the ABI-encoded calldata for:
authorizer.grantRoles(
[
0xeba777d811cd36c06d540d7ff2ed18ed042fd67bbf7c9afcf88c818c7ee6b498,
0x1282ab709b2b70070f829c46bc36f76b32ad4989fecb2fcb09a1b3ce00bbfc30,
],
0xC92E8bdf79f0507f65a392b0ab4667716BFE0110,
);
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 manageUserBalance((uint8, address, uint256, address, address)[])",
"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 = ["manageUserBalance", "batchSwap"]
.map(name => roleId("0xBA12222222228d8Ba445958a75a0704d566BF2C8", vault.getSighash(name)));
const vaultRelayer = "0xC92E8bdf79f0507f65a392b0ab4667716BFE0110";
const data = authorizer.encodeFunctionData("grantRoles", [roles, vaultRelayer]);
console.log(`authorizer.grantRoles(
[
${roles.map(role => `${role},`).join("\n ")}
],
${vaultRelayer},
);`);
console.log(data);
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 manageUserBalance((uint8, address, uint256, address, address)[])",
"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 = ["manageUserBalance", "batchSwap"]
.map(name => roleId("0xBA12222222228d8Ba445958a75a0704d566BF2C8", vault.getSighash(name)));
const vaultRelayer = "0xC92E8bdf79f0507f65a392b0ab4667716BFE0110";
const data = authorizer.encodeFunctionData("grantRoles", [roles, vaultRelayer]);
console.log(`authorizer.grantRoles(
[
${roles.map(role => `${role},`).join("\n ")}
],
${vaultRelayer},
);`);
console.log(data);