We are proposing a lightweight protocol for messaging Uniswap governance decisions from Ethereum to other chains.
The goal of this architecture is to use existing tools and battle-tested infrastructure wherever possible.
Governance still happens using the existing GovernerBeta contract and Uniswap’s UI.
The GovernerBeta feeds into the GovernanceMessenger contract on Ethereum, which serialises the requests as defined in the types below and passes them into the Ethereum Wormhole endpoint.
Wormhole produces a VAA (verifiable action approval) for this message, which can now be submitted to the GovernanceMessageReceiver contract on the target chain. The GovernanceMessageReceiver contract verifies the authenticity of the VAA using the local wormhole endpoint and passes the instruction into a local Timelock contract, which owns the local Factory.
A Timelock contract is put on each individual chain to add an optional layer of control over the universal bridge into the system. As an extra signer, the chains native bridge can act as an escape hatch by which pending proposals in the Timelock can be canceled.
interface IUniGovTypes {
// Factory Governance Actions
struct SetFactoryOwner {
uint16 chainId;
address factoryAddress;
address newOwner;
}
struct EnableFeeAmount {
uint16 chainId;
address factoryAddress;
uint24 fee;
int24 tickSpacing;
}
// Pool Governance Actions
struct setProtocolFee {
uint16 chainId;
address poolAddress;
uint8 feeProtocol0;
uint8 feeProtocol1;
}
struct collectProtocolFee {
uint16 chainId;
address poolAddress;
address recipient;
uint128 amount0Requested;
uint128 amount1Requested;
}
// General Timelock Actions
struct QueueTransaction {
uint16 chainId;
address target;
uint256 value;
bytes data;
}
struct CancelPendingTransaction {
uint16 chainId;
bytes32 id;
}
}
interface IGovernanceMessenger {
/// @notice Change the ownership of a factory contract on a specific chain
/// @param chainId The chainId this should get executed on
/// @param factoryAddress The address of the factory on that chain
/// @param newOwner The address of the new owner
function setFactoryOwner(
uint16 chainId;
address factoryAddress;
address newOwner;
) external;
/// @notice Enable a fee amount on a set of chains
/// @param chainId The chainId this should get executed on
/// @param factoryAddress The address of the factory on that chain
/// @param fee The fee amount to enable, denominated in hundredths of a bip (i.e. 1e-6)
/// @param tickSpacing The spacing between ticks to be enforced for all pools created with the given fee amount
function enableFeeAmount(
uint16 chainId;
address factoryAddress;
uint24 fee;
int24 tickSpacing;
) external;
/// @notice Set the denominator of the protocol's % share of the fees
/// @param chainId The chainId this should get executed on
/// @param poolAddress The address of the pool to execute the governance action on
/// @param feeProtocol0 new protocol fee for token0 of the pool
/// @param feeProtocol1 new protocol fee for token1 of the pool
struct setProtocolFee {
uint16 chainId;
address poolAddress;
uint8 feeProtocol0;
uint8 feeProtocol1;
}
/// @notice Collect the protocol fee accrued to the pool
/// @param chainId The chainId this should get executed on
/// @param poolAddress The address of the pool to execute the governance action on
/// @param recipient The address to which collected protocol fees should be sent (on the target chain)
/// @param amount0Requested The maximum amount of token0 to send, can be 0 to collect fees in only token1
/// @param amount1Requested The maximum amount of token1 to send, can be 0 to collect fees in only token0
struct collectProtocolFee {
uint16 chainId;
address poolAddress;
address recipient;
uint128 amount0Requested;
uint128 amount1Requested;
}
/// @notice Queue a transaction on the timelock contract
/// @param chainId The chainId this should get executed on
/// @param target The address to call
/// @param value to pass with the call
/// @param data The data to pass with the call
struct queueTransaction {
uint16 chainId;
address target;
uint256 value;
bytes data;
}
/// @notice Cancel a transaction on the timelock contract
/// @param chainId The chainId this should get executed on
/// @param id The id of the call to cancel
struct cancelPendingTransaction {
uint16 chainId;
bytes32 id;
}
}
interface IGovernanceMessenger {
/// @notice Submit a VAA containing a serialised governance action to the Timelock contract
/// @param vaa The VAA from the GovernanceMessenger contract
function submitActionToTimelock(
bytes vaa
) external;
}