Reason#
AAVE recently passed a governance update on interest rates, which resulted in the freezing of the BTC ETH USDT MATIC pools on Polygon, affecting $110 million in funds. For details, see: BlockSec Tweet, Technical Analysis, Official Statement.
The official statement claims that the issue can only be fixed through governance voting, requiring at least a 7-day wait, and they do not intend to use emergencyAdmin privileges.
Naturally, one might wonder how AAVE's cross-chain governance works? How is it technically implemented? Is it really necessary to wait 7 days?
Full Process from Proposal to Execution#
Taking the problematic proposal 224 as an example, let's look at the entire timeline of this proposal.
1. 2023/4/22 Forum Discussion#
2. 2023/4/29~2023/5/6 Snapshot Off-chain Voting#
Voting started on 2023/4/29 and ended on 2023/5/6.
A total of 15,854 people participated in the voting, with 574K AAVE in favor (99.93%).
3. 2023/5/10 Payload Contract Deployment#
This proposal modified the interest rate models on both the ETH and Polygon chains.
On 2023/5/10, the Payload contract was deployed on ETH 0x24bdacf6bbebaf567123da16cdb79a266597e92b, with new interest rate contracts such as the USDT interest rate contract deployed on 2023/5/8.
On 2023/5/10, the Payload contract was deployed on Polygon 0xF22c8255eA615b3Da6CA5CF5aeCc8956bfF07Aa8, with new interest rate contracts such as the USDT interest rate contract deployed on 2023/5/7.
4. 2023/5/11 On-chain Proposal Submitted#
This on-chain proposal was submitted by a multi-signature wallet.
https://etherscan.io/tx/0x05152e33582d861b7047254680588b249783be8d680936423f91bb90a74717cf#eventlog
The block height at which the proposal was submitted was 17240283, voting started at height 17247483 (7200 blocks apart), and ended at height 17266683 (19200 blocks during the voting period).
The Executor is 0xEE56e2B3D491590B5b31738cC34d5232F378a8D5, which is actually the global admin contract for AAVE.
The proposal has two actions to execute:
- Use delegatecall to execute the Payload contract on ETH 0x24db...e92b's
execute()
. - Use delegatecall to execute the CrosschainForwarderPolygon contract on ETH 0x158a...d45b's
execute(0xF22C8255EA615B3DA6CA5CF5AECC8956BFF07AA8)
, passing the address of the Payload contract to be executed on Polygon as a parameter.
5. 2023/5/11 ~ 2023/5/15 On-chain Voting#
https://app.aave.com/governance/proposal/?proposalId=224
Conditions that need to be met for the voting to pass:
- Quorum 320K
- Differential 80K
A total of 11 addresses completed voting, such as the voting transaction from aavechan.eth.
6. 2023/5/15 Waiting for Execution on ETH Chain#
After the voting ended, queue(224)
needs to be called to change the proposal from a successful voting state to a Queued state:
https://etherscan.io/tx/0x7dbfef2616f1efa17328d4bfe97b7bfd7e72f05d9f413c64a3c934278f54d242
This transaction was triggered by ChainLink Keeper, and then it can be executed on-chain after waiting until time 1684280195.
Note that there is also a concept of a grace period; if the proposal is executable but exceeds the grace period (432000 seconds = 5 days) without execution, it enters an expired state, and the proposal can no longer be executed.
7. 2023/5/17 Execution on ETH Chain#
Transaction for executing the proposal: https://etherscan.io/tx/0xe0e0ab8f7524f0165e6cb1182db18cf3ffe1c4f7cb57f335b40a322d8026de3f timestamp 1684284539.
This was also triggered by ChainLink Keeper, with EthRobotKeeper responsible for calling the Governance contract's execute(224)
.
Then it is handed over to Executor.executeTransaction using delegatecall to call the payload contract.
Remember that the Executor contract is the global admin, so through this method, it can execute any operation.
How is the execution message on ETH transmitted to Polygon? This involves using the official Polygon bridge.
CrosschainForwarderPolygon will be called by the Executor via delegatecall to invoke the official Polygon bridge FX_ROOT_ADDRESS = 0xfe5e5D361b2ad62c541bAb87C45a0B9B018389a2, passing the message to the target contract on Polygon POLYGON_BRIDGE_EXECUTOR = 0xdc9A35B16DB4e126cFeDC41322b3a36454B1F772.
Thus, from the perspective of the official Polygon bridge, the message sender is still the Executor's address 0xee56...a8d5, this admin address.
After the official Polygon bridge contract receives this call, it will emit the StateSynced(counter, receiver, data)
event, where counter is a globally incrementing counter, receiver is fixed to fxChild, and data is the abi encoding of (msg.sender, _receiver, _data)
. An example here is:
Decoding the data can yield:
msg.sender = 0xee56e2b3d491590b5b31738cc34d5232f378a8d5
receiver = 0xdc9a35b16db4e126cfedc41322b3a36454b1f772
data = 00000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001a000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000f22c8255ea615b3da6ca5cf5aecc8956bff07aa800000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000009657865637574652829000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001
What is this long data for?
8. 2023/5/17 Message Received on Polygon Chain#
The official Polygon bridge will call the FxChild contract 0x8397259c983751DAf40400790063935a11afa28a as the address 0x0000000000000000000000000000000000001001
, and then call the receiver's processMessageFromRoot(stateId, rootMessageSender, data)
function.
https://polygonscan.com/tx/0x52ed6243fc44b0b3999685020ca4b7b42c2629582194789a25792c044743dd59
This transaction is a Polygon system transaction, so both sender and to are the zero address, and it also contains other unrelated cross-chain transactions.
Timestamp 1684285952 shows that this cross-chain message transmission took 23 minutes, which is quite fast.
After receiving the message, it enters the receiver PolygonBridgeExecutor 0xdc9a...f772's processMessageFromRoot function, which verifies that msg.sender must be the cross-chain bridge FxChild, and the message source must be _fxRootSender, which is the admin contract on ETH.
It can be seen that the data is actually the abi encoding of five arrays: targets, values, signatures, alldatas, withDelegates, so we can decode it to get:
targets: (('0xf22c8255ea615b3da6ca5cf5aecc8956bff07aa8',)
values: (0,)
signatures: ('execute()',)
calldatas: (b'',)
withDelegatecalls: (True,)
Here, the targets refer to the Payload contract.
After receiving the message, it will not execute immediately; there is also a delay waiting period of 172800 = 2 days, and similarly, there is a 3-day grace period.
9. 2023/5/19 Execution on Polygon Chain#
https://polygonscan.com/tx/0x6172ae452fb2c6abab7aef013e33cf899e3de09e4933433eeeae5fa41be46457
This was also triggered by ChainLink Keeper, causing the PolygonBridgeExecutor to execute the payload contract via delegatecall, completing the parameter changes.
Thus, the entire process indeed requires 7 days:
Considering governance times, if approved, the fix will be applied in approximately 7 days from now:
- 1 day of delay to start voting,
- 3 days of voting,
- 1 day of timelock on Ethereum
- and 2 extra days of timelock on Polygon.